Single Responsibility Principle
The Single Responsibility Principle, often abbreviated as “SRP,” was introduced by Uncle Bob Martin, and states:
Notes
A class should have only one reason to change.
Classes with fewer responsibilities are more likely to be reusable, easier to understand, and faster to test. They are easy to change and require fewer changes after being written.
Although this is a very simple principle at a glance, deciding whether or not any two pieces of behavior introduce two reasons to change is difficult, and obeying SRP rigidly can be frustrating.
Reason to change
One of the challenges in identifying reasons to change is that you need to decide what granularity to be concerned with.
Stability
Not all reasons to change are created equal.
As a developer, you know which changes are likely from experience or just common sense ⇒ Từ kinh nghiệm cá nhân để detect xem đâu là thứ dễ thay đổi?
Ví dụ:
- Regular expression are powerful but tricky beasts, so it’s likely that we’ll have to adjust our regular expression. It might be nice to encapsulate that some- where else, such as in a custom validator.
- Logic deliver message cho user có thể thay đổi (nền tảng như email/sms/Facebook/Line/…) ⇒ Nên dùng Inject Dependencies⭐, remove delivery logic từ model này ⇒ Test dễ hơn.
TODO: Bổ sung các ví dụ related to: framework, DB, …
Notes
The less sure you are about a decision, the more you should isolate that decision from the rest of your application.
Cohesion - Gắn kết
One of the primary goals of SRP is to promote cohesive classes. The more closely related the methods and properties are to each other, the more cohesive a class is.
Classes with high cohesion are easier to understand, because the pieces fit naturally together. They’re also easier to change and reuse, because they won’t be coupled to any unexpected dependencies.
Following this principle will lead to high cohesion, but it’s important to focus on the output of each change made to follow the principle. If you notice an extra responsibility in a class, think about the benefits of extracting that responsibility. If you think noticeably higher cohesion will be the result, charge ahead. If you think it will simply be a way to spend an afternoon, make a note of it and move on.
Responsibility Magnets
Every application develops a few black holes that like to suck up as much re- sponsibility as possible, slowly turning into God Classes.
It’s easy to get sucked into a responsibility magnet by falling prey to just-one-more syndrome. Whenever you’re about to add a new behavior to an existing class, first check the history of that class. If there are previous commits that show developers attempting to pull functionality out of this class, chances are good that it’s a responsibility over-eater. Don’t feed the problem; add a new class instead.
Tension with Tell, Don’t Ask
Extracting reasons to change can make it harder to follow Tell, Don’t Ask.
Eg:
Method charge
follows Tell, Don’t Ask. Vì chúng ta chỉ đơn giản “tell”: any Purchase
instance call charge
, không cần check bất cứ state nào trong Purchase
.
Tuy nhiên, việc này vi phạm SRP, vì Purchase
sẽ có nhiều hơn 1 lý do để thay đổi. Nếu rules charge_credit_card thay đổi, hoặc rule tính tổng số tiền charge thay đổi, class này cũng phải thay đổi.
Ta có thể thay đổi 1 chút để follow SRP bằng cách tách new class cho charge
method:
This class can encapsulate rules around charging credit cards and remain immune to other changes, thus following SRP. However, it now violates Tell, Don’t Ask, because it must ask the @purchase
for its total_amount
in order to place the charge.
These two principles are often at odds with each other, and you must make a pragmatic decision about which direction works best for your own classes.
Drawbacks
There are a number of drawbacks to following this principle too rigidly:
- As outlined above, following this principle may lead to violations of Tell, Don’t Ask.
- This principle causes an increase in the number of classes, potentially leading to shotgun surgery and vocabulary overload.
- Classes that follow this principle may introduce additional indirection, making it harder to understand high level behavior by looking at individual classes.