IN A IN A PRAGMATICAL* PRAGMATICAL* AND AND OBJECTIVE* OBJECTIVE* WAY, ALLOWING IT TO WAY, ALLOWING IT TO BE USED BE USED DIRECTLY IN OUR DAY-TO-DAY DIRECTLY IN OUR DAY-TO-DAY PROGRAMMING. PROGRAMMING.
only one responsibility. "Each so ware module should have one and only one reason to change" "Gather together the things that change for the same reasons. Separate those things that change for different reasons." "Same reason" means it originates from the same business person. (Really???)
amount of change. Minimize amount of code to read/write. So: Small classes. High probability of localized change. Low probability of change propagating to other classes.
inside an object make it more cohesive. Dependencies between objects make them more coupled. What are dependencies? Physical relationships, like a method calling another one, or method referencing a variable. Semantic relationships.
It is semantically coupled. High probability they change together. public final class CurrencyConverter { public Amount convert(Amount amount, Currency currency) { return new Amount( amount.getValue() * exchange.getDailyRate( amount.getCurrency(), currency), currency); } }
It is also a design smell. Much worse than physical coupling, since it is invisible (to the compiler). Changes are very likely to propagate through semantic couplings, sometimes in subtle and unexpected ways. Very o en facilitated by getters.
of an IoT network, in which we're writing the server code, with following requirements: A meter may receive requests for making a readout of current values. A meter may at any time update its capabilities. These capabilities have to be displayed on the web interface.
and physical coupling, very unmaintainable. Violation of SRP! public final class Meter { private boolean gzipSupported; private Encryption encryptionSupported; private X509Certificate encryptionCertificate; ... ...getters, setters... } public final class MeterView { public String displayMeter(Meter meter) { return "...html with meter.getGzipSupported(), meter.getEncryptionSupported(), meter.getX()..."; } }
details of design, at the same time Tags do not know Meter. public final class Meter { private boolean gzipSupported; private Encryption encryptionSupported; private X509Certificate encryptionCertificate; ... public Component display() { return new Tags( gzipSupported?new Tag("GZIP"), encryptionSupported?new Tag("ENC"), encryptionSupported.display(), ...); } }
UI is by it's nature tightly coupled/cohesive to the domain. UI is usually an important part of an application. UI is actually part of the requirements! Business people actually talk about the UI, it is part of the common understanding and vocabulary! Details of the UI don't have to be in the Domain!
requirements: User may register with username and password. User may authenticate herself with given password. At the registration an email should be sent as confirmation.
much. public class UserRepository { ... public void insert(User user) { sql.insert("into user ...", user.getUsername(), HashUtils.hash(user.getPassword()), user.getEmailAddress()); } public User select(String username) { return sql.select(...); } }
public void execute(User user) { sql.insert("into user ...", user.getUsername(), HashUtils.hash(user.getPassword()), user.getEmailAddress()); } } public class SelectUserCommand { public User execute(String username) { return sql.select(...); } }
most people Seems clean enough It is the beginning of the end! Leads to high fragmentation (one method per class designs). Leads to injection/dependency/testing hell.
decoupling, we actually have very tight coupling. Semantic as well as physical. No cohesion, or very little cohesion. Has very little to do Object-Orientation, this may or may not be a problem for some. Look at the vocabulary: User, UserManager, InsertUserCommand, SelectUserCommand, EmailNotificationService
code, cleanly separated. public final class SqlUser implements User { ... @Override public boolean authenticate(String password) { return new PasswordHash( sql.select("passwordhash from user...", ...)) .matches(password); } @Override public void register() { sql.insert("into user...", ...); } }
is not part of the language (like inheritance). Yet. public class DelegatingUser implements User { private final User delegate; public DelegatingUser(User delegate) { this.delegate = delegate; } @Override public boolean authenticate(String password) { return delegate.authenticate(password); } @Override public void register() { delegate.register(); } }
public User createUser(String username, String password, String emailAddress) { return new NotifiedUser(username, emailAddress, new SqlUser(username, password, emailAddress)); }
Instead of separating per technology, it separates based on vertical features. Slight but crucial difference. Objects are cohesive and truly decoupled. I.e. SRP. Each feature is testable. It is OO. No data is pulled out of objects.
DO FOR YOUR CODE: 1. Make sure your class is physically cohesive. Methods and fields refer to each other. Don't forget the Constructor! 2. Make sure the physical coupling is not stronger than the physical cohesion. 3. Make sure you have your data, and don't need to get your data! This avoids semantic coupling. 4. Don't be dogmatic, UI (HTML, JSON/HTTP, SOAP) is functionality too!
with substitutions public class Person { // Model private String name; private int age; private String address; ...setters, getters... } public class PersonController { public void greet(Person person) { ... } public void add(Person person) { ... } }
decoupling, it actually increases coupling. Everything is strongly coupled to the data object, both controller and view. The whole thing must change if Person changes. Only allows limited "view" changes to be localized. Makes code unmaintainable.
Controller = Abstract UI Component View = HTML the Component reads public final class Person { // Model private final String name; private final int age; private final String address; ... public Component displaySummary() { return new InfoPanel() .addInfo("Name", name) .addInfo("Age", age) .addInfo("Address", address); } }
what we should mean by cohesion! public final class Person { // Model private final String name; private final int age; private final String address; public Person(String name, int age, String address) { ... } ... public InputComponent<Person> displayInput() { return new InputGroup() .add(new TextInput("Name", name)) .add(new NumberInput("Age", age, ...)) .add(new TextInput("Address", address)) .map(Person::new); } }
{ // Model private final String name; private final int age; private final Address address; public Person(String name, int age, Address address) { ... } ... public InputComponent<Person> displayInput() { return new InputGroup() .add(new TextInput("Name", name)) .add(new NumberInput("Age", age, ...)) .add(address.displayInput()) .map(Person::new); } }
1/4 of the Application Is a technical design, business-agnostic. Layers usually leak data upwards and create coupling (DTOs) Layers therefore tightly coupled to layers below. ⇒ Changes escalate and expand upwards!
for the ubiquitous language. The same language in code as between people. ⇒ Responsibilities can not be arbitrary, have to come from the business as well. Among other things: Persistence, Json/XML Formatting, Validation, Rules, Commands, etc., are therefore usually not valid responsibilities.
on Cohesion and Coupling DDD implies responsibilities are not arbitrary, have to come from the requirements. Using SRP to increase Maintainability leads to a different design than most are familiar with. Among others: UI, MVC and Layered Architectures have superior alternative interpretations for most cases.