I applied principles of the GRASP and ended up having a class called Environment.
This class’s responsibilities are to:
- Keep information about services in the environment,i.e. environment definition (Service is another class)
- Start/stop services meeting some criteria
- Apply configuration changes to different service and keep list of updated services
- Restart services with configuration change
- Revert configuration changes at the end of session
- Search/Give Service class based on criteria (name etc.)
According to OOD, this is not a problem: very cohesive responsibility is assigned to this class.
But on the other hand it’s a problem since the class is too big, even though all responsibility assigned to it makes sense. If I want to divide this responsibility to between separate classes, then all these classes need to know about “environment definition”, which makes coupling worse and those classes will have “feature envy”.
What design patterns are applicable for such situation? Or what other principals can be applied to have cohesive, less coupled classes?
Thanks in advance.
2
You probably meant to have Environment
as a Facade (facade pattern) and not to implement every detail in it.
Break out all functionality into other classes. Imho each of the responsibilities below should be handled by a separate class that the Environment
class uses internally.
- Keep information about services in the environment,i.e. environment definition (Service is another class)
- Start/stop services meeting some criteria
- Apply configuration changes to different service and keep list of updated services
- Restart services with configuration change
- Revert configuration changes at the end of session
- Search/Give Service class based on criteria (name etc.)
Those should give you six new classes. It will probably lead to some other classes too so that you for instance can manage configuration from different classes.
3
I’m not sure what design-pattern this is (if any), but in this particular case (if I understand this correctly) I would go for several classes, each for its own set of responsibilities plus front-end class which would serve as a proxy to those who responsible for a particular action.
Yes, the functionality is cohesive, but that does not mean you cannot decompose it to several cohesive and less complex classes, does it?
2
For one, keep in mind that classes do not do things to other classes as in “Apply configuration changes to different service”.
I would see the above as a ConfigurationChange class with potentially several derived types such as DbChange, PropertyFileChange, etc. Service would be a class with an applyConfigurationChange(ConfigurationChange) method. The environment would mostly be a coordinator, sensing when a ConfigurationChange has occurred, creating the appropriate ConfigurationChange instance and passing it to the effected services.
It also sounds like each Service would “have a” configuration stack which would support a pushConfiguration() and a popConfiguration() methods in order to support numbers 4 and 5 above.
The important thing is to keep your classes singularly focused – if they are performing more than one overall behavior, you should consider breaking each type of behavior out into a specific class – either to encapsulate complexity or for re-use. I like to think of it like this – code embedded in a class that performs some functionality may work great, but you can’t use it anywhere else and you can’t relocate it easily when something changes. Code in a well designed class that performs as advertised (i.e. each function does what it says it does), is easily moved around and can be trusted. By making behaviors a separate class, you are giving the behavior an API that separates it from the client class, providing your desired decoupling.
Finally, avoid any class that looks like or operate like a Manager class. In almost all instances I have seen, these classes become huge, bloated monsters. Rather than have a class that manages a set of classes, design each class to manage itself. Their typically is no class that knows more about itself than itself, so it should be the one managing itself. This is especially true of classes that are Threaded.
Another technique which can break up huge monolithic classes is to consider the individual activities that the class performs as unique classes – classes that are instantiated to handle a specific activity, gathering in the required instances, performing the work and then dissolving away – sort of like a dispatch pattern.