Grom's Homepage

Information Expert

Problem: What is the general principle of assigning responsibilities to objects?

Solution: Assign a responsibility to the information expert – the class that has the information necessary to fulfil the responsibility

Discussion: Fulfilment of a responsibility often requires information that is spread across different classes of objects. Whenever information is spread across objects, they will need to interact via messages to share the work.

Contraindications: Persistence should be handled by a persistence service. This contraindicates Information Expert


Creator

Problem: Who should be responsible for creating a new instance of some class?

Solution: Assign class B the responsibility to create an instance of class A if one or more of the following is true:

Contraindications: Often, creation requires significant complexity, such as using recycled instances for performance reasons, conditionally creating an instance from one of a family of similar classes based upon some external property value, and so forth. In these cases, it is advisable to delegate creation to a helper class called a Factory rather than follow the Creator pattern.


Low Coupling

Problem: How to support low dependency, low change impact, and increased reuse?

Coupling is a measure of how strongly one element is connected to, has knowledge of, or relies on other elements. An element with low coupling is not dependent on too many other elements.

A class with high coupling may suffer from the following problems:

Solution: Assign a responsibility so that coupling remains low

Discussion: Low Coupling is a principle to keep in mind during all design decisions; it is an underlying goal to continually consider. In object-oriented languages such as Java, common forms of coupling from Type_X to Type_Y include:

Contraindications: High coupling to stable elements and to pervasive elements is seldom a problem (e.g. The java libraries) because they are stable and widespread. It is not high coupling per se that is the problem; it is high coupling to elements that are unstable in some dimension, such as their interface, implementation, or mere presence.

Benefits:


High Cohesion

Problem: How to keep complexity manageable?

Cohesion is a measure of how strongly related and focused the responsibilities of an element are. An element with highly related responsibilities, and does not do tremendous amount of work, has high cohesion.

A class with low cohesion does many unrelated things, or does too much work. Such classes are undesirable, suffering from the following problems:

Solution: Assign a responsibility so that cohesion remains high.

Discussion: Like Low Coupling, High Cohesion is a principle to keep in mind during all design decisions; it is an underlying goal to continually consider. A class with high cohesion is easier to maintain, understand and reuse.

As a rule of thumb, a class with high cohesion has a relatively small number of methods, with highly related functionality, and does not do too much work. It collaborates with other objects to share the effort if the task is large

Bad cohesion usually begets bad coupling, and vice versa


Controller

Problem: Who should be responsible for handling an input system event?

An input system event is an event generated by an external actor. They are associated with system operations – operations of the system in response to system events, just as messages and methods are related.

A Controller is a non-user interface object responsible for receiving or handling a system event. A Controller defines the method for the system operation.

Solution: Assign the responsibility for receiving or handling a system event to a class representing one of the following:

Discussion: The controller is a kind of facade into the domain layer from the interface layer. A controller should delegate to other objects the work that needs to be done; it coordinates or controls the activity. It does not do much work itself.

Benefits:


Polymorphism

Problem: How to handle alternatives based on type? How to create pluggable software components?

Alternatives based on type – Conditional variation is a fundamental theme in programs. If a program is designed using if-the-else or case statement conditional logic, then if a new variation arises, it requires modification of the case logic.

Pluggable software components – Viewing components in client-server relationships, how can you replace on server component with another, without affecting the client?

Solution: When related alternatives or behaviours vary by type (class), assign responsibility for the behaviour – using polymorphic operations – to the types for which the behaviour varies.

Discussion: Polymorphism is a fundamental principle in designing how a system is organized to handle similar variations. A design based on assigning responsibilities by Polymorphism can be easily extended to handle new variations.

Contraindications: Sometimes, developers design systems with interfaces and polymorphism for speculative “future-proofing” against an unknown possible variation. Be realistic about the likehood of variability before investing in increased flexibility.

Benefits:


Pure Fabrication

Problem: What object should have the responsibility, when you do not want to violate High Cohesion and Low Coupling, or other goals, but solutions offered by Expert (for example) are not appropriate?

There are many situations in which assigning responsibilities only to domain layer software classes leads to problems in terms of poor cohesion and/or coupling, or low reuse potential.

Solution: Assign a highly cohesive set of responsibilities to an artificial or convenience class that does not represent a problem domain concept.


Indirection

Problem: Where to assign a responsibility, to avoid direct coupling between two (or more) things? How to de-couple objects so that low coupling is supported and reuse potential remains higher?

Solution: Assign the responsibility to an intermediate object to mediate between other components or services so that they are not directly coupled.

“Most problems in computer science can be solved by another level of indirection” is an old age with particular relevance to object-oriented design.


Protected Variations

Problem: How to design objects, subsystems, and systems so that the variations or instability in these elements does not have an undesirable impact on other elements?

Solution: Identify points of predicted variation or instability; assign responsibilities to create a stable interface around them.

Discussion: Protected Variations is a root principle motivating most of the mechanisms and patterns in programming and design to provide flexibility and protection from variations.

Core mechanisms

Data-driven designs

Data-driven designs cover a broad family of techniques including reading codes, values, class file paths, class names, and so forth, from an external source in order to change the behaviour of, or “parameterize” a system in some way at runtime. Other variants include style sheets, metadata, property files, etc. The system is protected from the impact of data, metadata, or declarative variations by externalizing the variant.

Service Lookup

Service lookup includes techniques such as naming services (for example, JNDI) or traders to obtain a service (for example, UDDI for web services). Clients are protected from variations in the location of services, using the stable interface of the lookup service. Service Lookup is a special case of data-driven design.

Interpreter-driven designs

Interpreter-driven designs include rule interpreters that execute rules read from an external source, script or language interpreters that read and run programs, virtual machines, neural network engines that execute nets, constraint logic engines that read and reason with constraint sets, and so forth. This approach allows changing or parameterizing the behaviour of a system via external logic expressions.

Reflective or Meta-Level designs

The system is protected from the impact of logic or external code variations by reflective algorithms that use introspection and meta-language services. It may be considered a special case of data-driven designs.

The Liskov Substitution Principle

Liskov Substitution Principle (LSP) formalizes the principle of protection against variations in different implements of an interface, or subclass extensions of a superclass. To quote:
"What is wanted here is something like the following substitution property: If for each object o1 of type S there is an object o2 of type T such that all programs P defined in terms of T, the behaviour of P is unchanged when o1 is substituted for o2 then S is a subType of T"

Structure-Hiding designs

The “Don't talk to stranger” rule states that within a method, messages should only be sent to the following objects:


Adapter

Problem: How to resolve incompatible interfaces, or provide a stable interface to similar components with different interfaces?

Solution: Convert the original interface of a component into another interface, through an intermediate adapter object.


Factory

Problem: Who should be responsible for creating objects when there are special considerations, such as complex creation logic, a desire to separate the creation responsibilities for better cohesion, and so forth?

Solution: Create a Pure Fabrication object called a Factory that handles the creation.


Singleton

Problem: Exactly one instance of a class is allowed – it is a “singleton”. Objects need a global and single point of access.

Solution: Define a static method of the class that returns the singleton.


Strategy

Problem: How to design for varying, but related, algorithms or policies? How to design for the ability to change these algorithms or policies?

Solution: Define each algorithm/policy/strategy in a separate class, with a common interface.


Composite

Problem: How to treat a group or composition structure of objects the same way (polymorphically) as a non-composite (atomic) object?

Solution: Define classes for composite and atomic objects so that they implement the same interface.


Facade

Problem: A common, unified interface to a disparate set of implementations or interfaces – such as within a subsytem – is required. There may be undesirable coupling to many things in the subsystem, or the implementation of the subsystem may change. What to do?

Solution: Define a single point of contact to the subsystem – a facade object that wraps the subsystem. This facade object presents a single unified interface and is responsible for collaborating with the subsystem components.


Observer

Problem: Different objects are interested in the state changes of a object, and want to react in their own unique way when the object generates an event. Moreover, the publisher wants to maintain low coupling to the subscribers. What to do?

Solution: Define a “subscriber” interface. The publisher can dynamically register subscribers who are interested in an event, and notify them when an event occurs.