Reality often and more easily is perceived as a stream of information. Meanwhile, the object-oriented programming paradigms are inexorable. If you do not understand them enough and do not adapt to them, your model can be quite inflexible, hard to maintain and resistant to development.
I remember when I had to switch my way of thinking. It was not easy and I also remember that it surprised me all of a sudden. Just today I was faced with a challenge how to plant this idea in someone, given that he or she already has their habits. You will see that sometimes it’s better to move a few steps back and just forget about some things. Simply start over with a clean slate!
Forget about the data
Forget about the view
The first thing is not to start from filling class properties. I understand that you display these on a screen in your head. I realize that the most we see first are the attributes, and only then we think what to do with them. Ultimately, however, they are implementation details that we will want to hide (Unfortunately, common MVC served the bad habits here).
Forget about Beans
Objects are not subject to any artificial specifications. It is unlawful to impose no-args constructors. Constructors are the place to deliver specific dependencies to the object. (On a side note, the more of those dependencies you have, the more likely that the responsibility of your object is too large). The good old objects have properties and behavior (POJO).
In the second step, after the creation of a class, do not start to provide it with accessors and mutators. (Have you managed already to forget about the view? If not, it is the last moment. Serialization and deserialization of data are the technical aspects, which should not bother you now).
// here come no setters and getters
It is not that you certain that you will not need it. But start without them. Do not make the assumption that for each field you need getter and setter. Be aware that, if you provide them, they will become part of the public interface.
The Business Object is not a container for mutable data, and its job is not to transport data. However, if you go this route you will inevitably head to anemic anti-pattern arms (Anemic Model).
Forget about Entities
Start thinking about the entities as a business objects that provide appropriate services to achieve the target domain communicating through messages. The Business Objects are not derived from the Entity Relationship Diagram. Your model should be agnostic with respect to the database, and infrastructure should be externalized.
Do not think about the columns in the table, unless you just sit in the domain of reports. If it is a reporting domain, I would accept any query and any view to be a valid part of it (Query as opposed to Command; CQRS). When modelling Business Objects, it is good to focus just on Commands.
Think about public interface
Think about the design
Although most applications (especially web ones) are a simple CRUD, and that probably a lot of things can be done in a simpler way, without modelling, however, when it comes to object-oriented programming, the design is a very important thing. Only following the OOP principles can ensure the vitality of the system thanks to its adaptive mechanisms.
At this stage, do not confuse the abstract type (which is the interface) with a description of the interaction of business objects. For the formation of such a model, the CRC cards can be used:
These cards should be small enough to prevent a possible attempt of placing on them too many responsibilities of one object by forcing them to extract them to other objects.
They promote thinking about responsibilities and collaborators. In particular, it is good to remember here to tell the objects what to do and not to query them to take further decisions on the basis of a result (Tell, Do’t Ask). Instead of
int value = myObject.getValue();
myObject.setValue(value + 1);
do:
myObject.incrementValue();
This principle corresponds with another important one that refers to the interaction between objects. This is the Law of Demeter saying that objects should only talk to friends.
The selection of the model class names is equally important. In fact, these names should identify the roles of our objects. I remember more than one battle and more than one refactoring of class names, which always makes sense, if brings us closer to a better reflection of the actual domain problem. These names should be the result of brainstorming with the participation of business people in order to a language understandable to all engaged parties. (Ubiquitous Language)
Think of abstraction
Prototyping model using the Interface-Based Programming seems to be quite an interesting idea. It is good to start with the abstract types, namely interfaces, and then possibly the next step should be refactoring them to classes, while concretising our model. When defining the interface it’s good to remember about :
- minimization of methods and their arguments; restrict it to a minimum
- a clear separation of commands – the methods that do not return a result, and queries – methods that return a result (CQS)
Relying on abstraction, not on the details of implementation is another important principle of OOP referred to as Dependency Inversion. Try to provide dependencies by the interface through the constructor and avoid creating them internally, so that they are always explicit.
Thanks to abstraction and the above rule it is possible to implement the architecture of ports and adapters or plug-ins, providing flexibility and interchangeability of the components.
Think about the context
Domain Driven Design introduced the concept of Bounded Context, later included in the list of microservices principles. The idea is to identify models describing different aspects of business and keep it consistent within certain limits; focus on a particular business problem and forget about the outside world and potentially a broader sense. A typical example discusses PRODUCT
object, which actually may have a completely different meaning in different contexts – for example, in different departments of the same company.
Business context coincides with responsibilities. Single Responsibility Principle does not say that the object has to have only one method but that all methods of objects, indeed all collaborators, work in favor of one business – the owner or authorized group of people (SOLID).
Think about composition
Speaking of abstraction, it is impossible not to recall the principle to favor Composition over Inheritance. Actually, it highlights everything I called design based on simple interfaces and building relationships based on them. As a result, it promotes the relationship HAS-A . Do not think, therefore, about family of PRODUCT
objects in a tree, and focus on selected branches or later extract the common features to separate objects – dependent entities or Value Objects e.g. PersonName
grouping fristName
, middleName
and LastName
.
Having mentioned Value Object it is good to talk about its features. Firstly, it is identified by all its properties (equals and hashcode based on them) unlike “entity” based on unique id. Secondly, it is immutable ! And yes, it knows everything about processing itself.
public Quantity add(Quantity quantity) {
return new Quantity(this.q + quantity.q);
}
Finally, the composition is used almost everywhere from the more complex Business Objects, Aggregate Roots, Domain Services to any other layer. It is promoted by a number of design patterns.
Summary
This article is the result of reflections forced by the challenge that I made joining the Tech Jump program. It is a development of the mind map created for these needs. Of course, it does not exhaust any of the topics, and is only a metaphor indicating related elements of a wider domain, which is the object-oriented programming. The intention of OOP is to increase the reuse and maintenance of the code through its modularization.
So how to think in an object-oriented way ? Here’s the recipe:
- identify the public interface
- draw a model
- talk about class names, responsibilities and collaborators
- name objects with nouns
- Tell, Don’t Ask
- use the commands
- use verbs
- send messages
- take care of their immutability
- limit the interface
- number of methods and arguments
- extract redundancy
- compose new cards
- do not organize them hierarchically by default
- group common parts
- start with Interface-based programming
- later transform interfaces in classes
- use constructors to inject dependencies
- think about the identification of business objects
- aggregate Business Objects into new ones
- if the responsibility does not belong to only one Business Object
- support one track solution
- work in the narrow context
- refactor and adapt
Further reading
- http://www.martinfowler.com/articles/injection.html#ConstructorVersusSetterInjection
- http://www.javaworld.com/article/2073723/core-java/why-getter-and-setter-methods-are-evil.html
- http://c2.com/doc/oopsla89/paper.html#cards
- http://udidahan.com/2007/04/21/domain-model-pattern/
- http://alistair.cockburn.us/Hexagonal+architecture
- https://sourcemaking.com/design_patterns