Valid C # Principle 33: Restrict Access
Item 33: limit visibility of your types
Not all people need to know everything. Not all types must be public. For each type, the access level should be restricted as much as possible when the function is met. In addition, these access levels are often much less than you think. In a private type, all users can access the functions defined by this interface through a public interface.
Let's go back to the most fundamental situation: powerful tools and lazy developers. Vs.net is a great high-yield tool for them. I use vs.net or C # builder to easily develop all my projects, because it allows me to complete tasks faster. One of the enhanced high-yield tools is to allow you to click only two buttons and create a class. Of course, if this is what I want. The classes created by vs.net for us are as follows:
Public class class2
{
Public class2 ()
{
//
// Todo: Add constructor logic here
//
}
}
This is a public class, which usesProgramSetCodeBlocks are visible. This visibility level is too high, and many independent classes should be internal (internal. You can restrict access by embedding a protected or private class in an existing class. The lower the access level, the less likely it will be to update the entire system in the future. The fewer places you can access the type, and the fewer places you need to modify when updating.
Expose only the content to be exposed. Try to implement public interfaces on the class to reduce the visible content. You can find an example using the enumerator mode in the. NET Framework library. system. arraylist contains a private class, arraylistenumerator, which only implements the ienumerator interface:
// Example, not complete source
Public class arraylist: ienumerable
{
Private class arraylistenumerator: ienumerator
{
// Contains specific implementation
// Movenext (), reset (), and current.
}
Public ienumerator getenumerator ()
{
Return new arraylistenumerator (this );
}
// Other arraylist members.
}
For users like us, we do not need to know the arraylistenumerator class. All you need to know is that when we call the getenumerator function on the arraylist object, what you get is an object that implements the ienumerator interface. The specific implementation is a clear class .. Net Framework designers use the same pattern in another collection class: the hash table (hashtable) contains a private hashtableenumerator, the queue (Queue) contains a queueenumerator, and so on. Private enumeration classes have more advantages. First, the arraylist class can completely replace the type that implements ienumerator, And you have become a wise programmer without damaging any content. In fact, the enumerator class must not be CLS compatible because it is not public (see Principle 30 ). Its public interfaces are compatible. You can use the enumerator without having to know any details about the implemented class.
Creating internal classes is a commonly used method to limit the visible range of types. By default, many programmers always create public classes and never consider other methods. This is about vs.net. We should replace the default without thinking. We should carefully consider where your type will be used. Is it visible to all users? Or is it mainly used inside an assembly?
By using interfaces to expose functions, you can create internal classes more easily without limiting their usage outside the Assembly (see Principle 19 ). What type should be public? Or is there a better interface aggregation to describe its functions? Internal classes allow you to replace a class with different versions, as long as they implement the same interface. For example, consider the problem of phone number verification:
Public class phonevalidator
{
Public bool validatenumber (phonenumber pH)
{
// Perform validation.
// Check for valid area code, exchange.
Return true;
}
}
After a few months, this class can still work well. When you get an international phone number request, the previous phonevalidator fails. It only targets us phone numbers. You still need to verify the US phone number. Now, you need to verify the international phone number during installation. Instead of pasting additional function code into a class, it is better to cut off two different content coupling practices and directly create an interface to verify the phone number:
Public interface iphonevalidator
{
Bool validatenumber (phonenumber pH );
}
Next, modify the existing phone verification, and use the interface to implement it as an internal class:
Internal class usphonevalidator: iphonevalidator
{
Public bool validatenumber (phonenumber pH)
{
// Perform validation.
// Check for valid area code, exchange.
Return true;
}
}
Finally, you can create a class for international phone number verification:
Internal class internationalphonevalidator: iphonevalidator
{
Public bool validatenumber (phonenumber pH)
{
// Perform validation.
// Check international code.
// Check specific phone number rules.
Return true;
}
}
To achieve this, you need to create an appropriate class, which is based on the telephone number type class. You can use the factory-like mode to implement this idea. Only interfaces are visible outside the assembly. The actual class is the special class used for different regions in the world, which is visible only within the Assembly. You can create different verification classes for verification in different regions, without worrying about other assemblies in the system.
You can also create a public abstract class for phonevalidator, which contains the implementation of universal verification.Algorithm. Users should be able to access public functions through the base class of the Assembly. In this example, I prefer to use public interfaces, because even with the same functions, this is relatively less. Others may prefer abstract classes. No matter which method is used, as few public classes as possible in the program set.
These exposed public classes and interfaces are your contract: You must keep them. The more chaotic interfaces are exposed, the more you will be restricted in the future. The fewer public types are exposed, the more options are available in the future to expand or modify any implementations.
==============================================
Item 33: limit visibility of your types
Not everybody needs to see everything. not every type you create needs to be public. you shoshould give each type the least visibility necessary to accomplish your purpose. that's often less visibility than you think. internal or private classes can implement public interfaces. all clients can access the functionality defined in the public interfaces declared in a private type.
Let's get right to the root cause: powerful tools and lazy developers. vs. net is a great productivity tool. I use it or C # builder for all my development simply because I get more done faster. one of the productivity enhancements lets you create a new class with two button clicks. if only it created exactly what I wanted. the class that. net creates looks like this:
Public class class2
{
Public class2 ()
{
//
// Todo: Add constructor logic here
//
}
}
It's a public class. it's visible to every piece of code that uses the Assembly I'm creating. that's usually too much visibility. using standalone classes that you create shoshould be internal. you can further limit visibility by creating protected or private classes nested inside your original class. the less visibility there is, the less the entire system changes when you make updates later. the fewer places that can access a piece of code, the fewer places you must change when you modify it.
Expose only what needs to be exposed. try implementing public interfaces with less visible classes. you'll find examples using the enumerator pattern throughout.. NET Framework library. system. arraylist contains a private class, arraylistenumerator, that implements the ienumerator interface:
// Example, not complete source
Public class arraylist: ienumerable
{
Private class arraylistenumerator: ienumerator
{
// Contains specific implementation
// Movenext (), reset (), and current.
}
Public ienumerator getenumerator ()
{
Return new arraylistenumerator (this );
}
// Other arraylist members.
}
client code, written by you, never needs to know about the class arraylistenumerator. all you need to know is that you get an object that implements the ienumerator interface when you call the getenumerator function on an arraylist object. the specific type is an implementation detail. the. net Framework designers followed this same pattern with the other collection classes: hashtable contains a private hashtableenumerator, queue contains a queueenumerator, and so on. the Enumerator class being private gives extends advantages. first, the arraylist class can completely replace the type implementing ienumerator, and you 'd be none the wiser. nothing breaks. also, the enumerator class need not be CLS compliant. it's not public (see item 30 .) its public interface is compliant. you can use the enumerator without detailed knowledge about the class that implements it.
Creating internal classes is an often overlooked method of limiting the scope of types. by default, most programmers create public classes all the time, without any thought to the alternatives. it's that. net wizard thing. instead of unthinkingly accepting the default, you shoshould give careful thought to where your new type will be used. is it useful to all clients, or is it primarily used Intern Ally in this one assembly?
Exposing your functionality using interfaces enables you to more easily create internal classes without limiting their usefulness outside of the Assembly (see item 19 ). does the type need to be public, or is an aggregation of interfaces a better way to describe its functionality? Internal classes allow you to replace the class with a different version, as long as it implements the same interfaces. As an example, consider a class that validates phone numbers:
Public class phonevalidator
{
Public bool validatenumber (phonenumber pH)
{
// Perform validation.
// Check for valid area code, exchange.
Return true;
}
}
Months pass, and this class works fine. then you get a request to handle international phone numbers. the previous phonevalidator fails. it was codedto handle only U. s. phone numbers. you still need the U. s. phone validator, But now you need to use an international version in one installation. rather than stick the extra functionality in this one class, you're better off tuning the coupling between the different items. you create an interface to validate any phone number:
Public interface iphonevalidator
{
Bool validatenumber (phonenumber pH );
}
Next, change the existing phone validator to implement that interface, and make it an internal class:
Internal class usphonevalidator: iphonevalidator
{
Public bool validatenumber (phonenumber pH)
{
// Perform validation.
// Check for valid area code, exchange.
Return true;
}
}
Finally, you can create a class for international phone validators:
Internal class internationalphonevalidator: iphonevalidator
{
Public bool validatenumber (phonenumber pH)
{
// Perform validation.
// Check international code.
// Check specific phone number rules.
Return true;
}
}
to finish this implementation, you need to create the proper class based on the type of the phone number. you can use the factory pattern for this purpose. outside the Assembly, only the interface is visible. the classes, which are specific for different regions in the world, are visible only inside the assembly. you can add different validation classes for different regions without disturbing any other assemblies in the system. by limiting the scope of the classes, you have limited the code you need to change to update and extend the entire system.
You coshould also create a public abstract base class for phonevalidator, which coshould contain common implementation algorithms. the consumers cocould access the public functionality through the accessible base class. in this example, I prefer the implementation using public interfaces because there is little, if any, shared functionality. other uses wocould be better served with public abstract base classes. either way you implement it, fewer classes are publicly accessible.
Those classes and interfaces that you expose publicly to the outside world are your contract: You must live up to them. the more cluttered that interface is, the more constrained your future ction is. the fewer public types you expose, the more options you have to extend and modify any implementation in the future.