Конспект POODR. Reducing Costs with Duck Typing
Некоторые ребята уверены, что ООП — это про инкапсуляцию, наследование и полиморфизм, а сами объекты — просто такая обертка над данными. Это не так, ООП — это про сообщения, которые объекты отправляют друг другу. А лучше всего об этом рассказано в POODR, Practical Object-Oriented Design in Ruby Сэнди Метц.
Это настолько полезная книга, что я перечитываю ее каждый год. Чтобы перестать уже ее перечитывать, хочу закрепить знания с помощью конспекта. В этом посте — конспект пятой главы, Reducing Costs with Duck Typing.
Осторожно: это мой субъективный конспект. Не забудьте прочитать оригинал, книга того стоит.
Утиная типизация
Утиные типы — это публичные интерфейсы, не привязанные к конкретным классам. Такие интерфейсы снижают стоимость зависимостей, опираясь на сообщения, а не на конкретные классы.
Про утиную типизацию написано так много, что дальше я просто даю интересные цитаты:
Users of an object need not, and should not, be concerned about its class. Class is just one way for an object to acquire a public interface; the public interface an object obtains by way of its class may be one of several that it contains. Applications may define many public interfaces that are not related to one specific class; these interfaces cut across class. Users of any object can blithely expect it to act like any, or all, of the public interfaces it implements. It’s not what an object is that matters, it’s what it does.
If your design imagination is constrained by class and you find yourself unexpectedly dealing with objects that don’t understand the message you are sending, your tendency is to go hunt for messages that these new objects do understand.
Про абстрактное и конкретное:
The concreteness of the first example makes it simple to understand but dangerous to extend. The final, duck typed, alternative is more abstract; it places slightly greater demands on your understanding but in return offers ease of extension. Now that you have discovered the duck, you can elicit new behavior from your application without changing any existing code; you simply turn another object into a Preparer and pass it into Trip’s prepare method.
This tension between the costs of concretion and the costs of abstraction is fundamental to object-oriented design. Concrete code is easy to understand but costly to extend. Abstract code may initially seem more obscure but, once understood, is far easier to change. Use of a duck type moves your code along the scale from more concrete to more abstract, making the code easier to extend but casting a veil over the underlying class of the duck.
Про полиморфизм:
Polymorphism in OOP refers to the ability of many different objects to respond to the same message. Senders of the message need not care about the class of the receiver; receivers supply their own specific version of the behavior.
A single message thus has many (poly) forms (morphs).
There are a number of ways to achieve polymorphism; duck typing, as you have surely guessed, is one. Inheritance and behavior sharing (via Ruby modules) are others, but those are topics for the next chapters.
Polymorphic methods honor an implicit bargain; they agree to be interchangeable from the sender’s point of view. Any object implementing a polymorphic method can be substituted for any other; the sender of the message need not know or care about this substitution.
Как находить утиные типы:
Using duck typing relies on your ability to recognize the places where your application would benefit from across-class interfaces. It is relatively easy to implement a duck type; your design challenge is to notice that you need one and to abstract its interface.
Утиные типы легко обнаружить, когда встречаете:
case
с условием, которое проверяет класс;kind_of?
,is_a?
;responds_to?
.
Use of kind_of?, is_a?, responds_to?, and case statements that switch on your classes indicate the presence of an unidentified duck. In each case the code is effectively saying “I know who you are and because of that I know what you do.” This knowledge exposes a lack of trust in collaborating objects and acts as a millstone around your object’s neck. It introduces dependencies that make code difficult to change.
Just as in Demeter violations, this style of code is an indication that you are missing an object, one whose public interface you have not yet discovered. The fact that the missing object is a duck type instead of a concrete class matters not at all; it’s the interface that matters, not the class of the object that implements it.
P. S. Ещё больше постов о программировании, тестах и культуре разработки у меня в Телеграме.