[RUBY] Read "Object-Oriented Design Practice Guide"

Statement of determination

I want to read it little by little over a month or two. This is a memo for myself.

The point

Outline and introduction of object-oriented programming

1. Object-oriented design

1.1 Design praise

--If you follow the object-oriented design method, you can enjoy coding and produce software most efficiently! --Object-oriented design = Managing dependencies. --Parts

1.2 Design tools

--Design principle "SOLID" --Design pattern "GoF"

1.3 Act of design

--The meaning of "design" is different between BUFD (waterfall?) And object-oriented design. - BUFD --Complete documentation that identifies future internal behavior. --Object-oriented design --Code structure in a small area, assuming changes.

1.4 Easy introduction of object-oriented programming

--Procedural language --Variables have one data type. --Data and behavior are different. --Object-oriented language --Objects have behavior. --Has many types (= classes). --You can predict the behavior of the object. --Object-oriented languages can extend types. Therefore, object-oriented applications will become a special and unique programming language handled by programmers.

Focus on the class

2. Design a single responsibility class

--There are two different criteria for the goal of modeling an application. --Use classes to perform the required actions "immediately". —— Make it easy to change “afterwards”. ――The part where programming technology is clearly shown.

2.1 Decide what belongs to the class

--The first step to writing TRUE code is to ensure that you have a single responsibility (= minimal and useful).

2.2 Create a single-responsibility class

--All classes and methods have a single responsibility. ――What is single responsibility? --When you paraphrase the method of the class into a question, it becomes a meaningful question. --You can explain the class in one sentence. --"It" and "or" are not included.

2.3 Write code that welcomes changes

--Hide instance variables and data structures = Change behavior (method). --Prevent unexpected changes from affecting your code. --Once all methods have a single responsibility, the scope of the class becomes clear.

3. Manage dependencies

--Types of object behavior

  1. Behavior that the class itself should implement independently
  2. Inheritance of behavior
  3. Behavior is implemented in other objects

3-1. Understand dependencies

--Dependencies exist when an object knows:

  1. Names of other classes
  2. The name of the message sent to an object other than self
  3. Arguments required by the message
  4. The order of those arguments

--Minimize dependencies so that code changes aren't big enough to force changes to other objects.

3-2. Write loosely coupled code

--Dependence is evil. --All addictions are like foreign bacteria that try to undermine the class (design).

  1. When there are names of other classes --Injecting dependent objects --You don't need to know the class name of other objects, and you can separate the join. --If you always keep in mind what you depend on and make it a habit to inject them, your class will naturally become loosely coupled. --Isolation of dependencies --Isolate the creation of instance variables of other classes from the initialization of self, and create them in the method defined independently in self. --Dependency becomes clear and the barrier to reuse is lowered. - ||=Operator (deferred instance creation)

  2. When there is a message sent other than self --Remove external dependencies and encapsulate in a dedicated method --The method becomes independent of external objects and depends on the message sent to self.

  3. When there are arguments requested by the message --Receive an option hash --All dependencies on the order of the arguments are removed. --The "key" name in the hash becomes the explicit documentation for the arguments. --The hash returns nil for non-existent keys, so you can explicitly set a default value. - ||operator, fetchMethod, mergeMethod

  4. When the order of arguments is fixed (when the method that must depend on is external, etc.) --Poke one method to wrap up the external interface. --Create a Wrapper module (=" factory "). --Objects whose purpose is to create other objects --By calling an external method within the factory method, you can avoid multiple dependencies on fixed-order arguments. --The factory is not intended to be instantiated. --Factory is not expected to be included in other classes.

3-3. Management of dependency direction

--Reverse dependencies --Selection of dependency direction -"Depend on what is unchanged from yourself" = Three facts that are the basis of the following concept

  1. Some classes have more variable requirements than others --Framework with active development, etc.
  2. Concrete classes are more likely to change than abstract classes --Injection of dependent objects, etc. --Dependence on abstractions is always safer than dependence on concrete ones.
  3. Changing dependent classes from many places has widespread impact --Avoid heavily dependent classes --Find problematic dependencies --If the concrete class has a lot of dependencies, you should sound a mental alarm.

--The key to maintenance is to control the direction of dependency and rely on a class that changes less than you.

From object-centric design to message-centric design

4. Create a flexible interface

--Design is not just about what an object knows (object responsibility) or who it knows (object dependencies). --How the objects talk to each other.

4-1. Understanding the interface

--Public interface --Methods exposed to the outside --The class implements methods, some of which are intended to be used by other objects.

4-2. Define the interface

--Characteristics of public interface

  1. Clarify the main responsibilities of the class
  2. Expected to be executed from the outside
  3. Not changed on a whim
  4. Safe for others to depend on it
  5. Fully documented in the test

--Characteristics of private interface

  1. Involved in implementation details
  2. Not expected to come from other objects
  3. Can be changed for any reason
  4. It is dangerous for others to depend on it
  5. May not be mentioned in the test

--The public interface is a contract that clearly states the responsibility of the class. --Marking a method as public or private tells the class user which method they are likely to depend on safely.

4-3. Find a public interface

--Application design --"Domain object" --A persistent class that represents a "noun" that has both "data" and "behavior". ――A large and visible real-world thing, and finally a database. --Domain objects are not central to designing applications.

--Pay attention to the messages exchanged between objects, not the objects. --You should first figure out both the "objects" and "messages" needed to meet your use case.

--Use sequence diagram ――Reveal the message exchange (public interface) between classes and ask, "Should this recipient be responsible for responding to this message?" --Classes and who and what they know => Decide on a message and where to send it -"I know I need this class, but what should I do?" => "I need to send this message, but who should respond?"

--Ask the recipient "what" instead of telling the sender "how" --Passing responsibility for knowing "how" to other classes --A small public interface means that there are only a few methods that depend on it elsewhere. --Improved code flexibility and maintainability.

--Searching for contextual independence --The best situation is that the object is completely independent of its context (keeping the object capable of responding to messages held by other classes). --Focus on the difference between "what" and "how" --Injecting dependent objects --Techniques for collaborating with someone else without knowing who they are ――I expect you to trust the recipient of the message and behave appropriately.

--The importance of trust -"I know what I want and I know how you do it" => "I know what I want and you I know what to do "=>" I know what I want and I believe you will do your part. " ――This trust of letting go is the key to object-oriented design

--Discovering the need for new objects --When the principle of single responsibility is violated. ――It tells how an object behaves to other objects and requires a huge amount of context. --A new object is discovered because it needed to send a message to it

4-4. Write the code that exposes the best side (interface)

――The interface defines the application and determines the future.

--Every time you create a class, declare the interface. --Methods included in the "public" interface

  1. Explicitly identify as a public interface
  2. "What" is more than "how"
  3. The name is immutable as far as we can think of
  4. Take a hash as an optional argument

--Ruby public, protected, private keywords ――By using these keywords, you can convey the following two things. ――I believe that I have better information now than the information that "future" programmers have. ――I believe that we must prevent future programmers from inadvertently using methods that we consider unstable now. ――A number of very talented Ruby programmers dare to omit keywords. --The idea that you can use keywords to restrict access to a method is just an illusion.

--When building a public interface, aim to minimize the context that the public interface requires from others. --Make message senders get what they want without knowing how the class implements that behavior. --Even if the person who wrote it first didn't define a public interface, create one yourself.

4-5. Demeter's Law

--A collection of coding rules for loosely coupling objects. --It means that the public interface has not been accurately identified and defined. ――There are times when there is no actual harm even if you violate the law. -"Let's talk only to our immediate neighbors" -"Let's use only one dot" --Get distant attributes through an intermediate object. --Explicitly identify intermediate objects and modify them as needed. --Use delegation. --delegate method ――Rethink the message chain based on what you are looking for ――You don't need to know "how". --If you move to a message-based perspective and find a message, that message naturally becomes the public interface of some object. ――The message itself will guide you to what the object is.

5. Reduce costs with duck typing

――What is "duck typing"? --Public interface that is not tied to any particular class -Just like a chameleon. "If an object sounds like a duck and walks like a duck, whatever its class, it's a duck."

5-1. Understanding Duck Typing

--Depending on the application, you may define several interfaces that span multiple classes. ――The important thing is not what the object is, but what it does

--The ability to overlook ambiguity about a class of objects proves to be a confident designer. --You will be able to treat an object as if it were defined by its behavior rather than its class.

Polymorphism

--A wide variety of objects also refer to the ability to respond to messages. --One message has many (poly) morphs.

5-2. Write code that trusts the duck

--Difficult design ――Aware that you need a duck type --Abstracting that interface

--How to recognize a hidden duck

  1. A case statement that branches in a class
  2. kind_of? and is_a? 3. responds_to?

--Ask what the argument of that method wants. ――The answer to that question shows the message to be sent. From this message, the underlying duck type begins to be defined. --Do not include unnecessary dependencies that you control rather than trust other objects. --Trust the duck. --Once you know the duck type, define the interface, implement it where you need it, and trust that it behaves correctly. --Document the duck type. --When creating a duck type, you must both document and test its public interface. --Share code between duck. --When defining common methods, share only the interface, not the implementation. --Often you need to share some behavior as well. --Choose a duck wisely --Understand that if the underlying duck requires a change to the basic Ruby classes, there are risks and trade-offs, and the purpose of the design is to reduce costs.

5-3. Overcome fear of duck typing

--Duck type invalidation by static typing

--Comparison of dynamic typing and static typing --Static typing --Compiler finds type errors at compile time <=> Run-time type errors occur unless the compiler checks for types --The visualized type information also serves as a document. Without the <=> type, the programmer cannot understand the code. Programmers cannot infer its type from the context of an object --Compiled code is optimized and runs fast <=> Without a set of optimizations, your application will run too slowly --Dynamic typing --The code is executed sequentially and loaded dynamically. Therefore, there is no compile / make cycle. <=> It is safer to have no compile / make cycle for whole application development --Source code does not contain explicit type information <=> It is easier for programmers to understand when the type declaration is not included in the code. The type of the object can be inferred from that context. --Easier metaprogramming <=> Metaprogramming is a desirable language feature

--It is the programmer's own intelligence to prevent type errors. The idea that static typing makes it safe is an illusion. ――The goodness of the code is the goodness of the test after all. ――Duck typing is based on dynamic typing.

6. Acquire behavior by inheritance

6-1. Understand class inheritance

--Inheritance is the mechanism of "automatic delegation of messages"

6-2. Identify where inheritance should be used

--You can find subclasses, just like you did when you found a duck type. --If statements, type, category, etc. that confirm "attributes that hold their own classification". --bad: "I know'who you are'because I know'what you do'" => indicates the existence of a subclass. --"Single inheritance". Subclasses can only have one parent superclass. --Subclasses are "specialized" superclasses. --Subclasses have all the public interfaces of superclasses.

6-3. Improperly apply inheritance

--Sending super will inherit unnecessary, totally meaningless behavior.

6-4. Find an abstraction

--Inheritance rules

  1. The object being modeled has a "generalization-specialization relationship"
  2. Use the correct coding technique -Make an abstract superclass --A superclass is not a complete object by itself. ――It is almost unthinkable to send a new message to the superclass. --There are also object-oriented programming languages that have a syntax that explicitly declares a class as an abstraction, such as the Java ʻabstract` keyword. --Ruby does not have such keywords due to the nature of trusting others. --Abstract classes exist for subclasses to be created, and this is their sole purpose. --Provide a common storage location for behavior shared between subclasses. --It doesn't make sense to create an abstract superclass that has only one subclass. --The easiest way to identify the correct abstraction is when there are at least three concrete classes in existence. -Promote abstract behavior --When refactoring into a new inheritance hierarchy, the code should be structured so that the abstraction can be promoted, not the concrete demoted structure. --Separate abstraction from concreteness. --Use the template method pattern. --A technique that defines the basic structure within a superclass and sends a message to get subclass-specific contributions. --Superclasses provide subclasses with a structure (common algorithm). --For all objects, some methods will use the same initial value, and some methods will use different initial values. --Make sure that every class that uses the template method pattern has an implementation for every message it sends. --Template method requirements are always documented by implementing matching methods that raise useful errors.

6-5. Manage the degree of coupling between superclasses and subclasses

--The tightly coupled classes are in close contact with each other, making it impossible to change each independently. --When a subclass sends a super, it is a declaration that it effectively knows the superclass's algorithm and is" dependent "on that knowledge.

--Use hook messages to loosely couple subclasses. --Rather than asking you to send a super, have the superclass send a" hook "message instead. --By using the hook method, allow the inheritor to provide specialization to the superclass without forcing the transmission of super.

7. Share the behavior of roles in modules

7-1. Understanding roles

--When originally unrelated objects take on a common role, the objects become related to each other. The duck type is a roll. --The method of giving a name and defining a group of methods is called a "module". --It's a perfect way for objects of different classes to play a common role with code defined in one place.

--The set of messages that an object can respond to includes the following four types of messages. --Messages that you implement --Messages implemented by all objects in the hierarchy above itself --Messages that are added to itself and implemented in all modules --Messages implemented in all modules added to objects in the hierarchy above itself

--Objects should manage themselves ――You should have your own behavior. Avoid adding unnecessary dependencies.

--Dependent objects are hidden by injecting them at initialization.

--By creating a module, other objects can use this module to take on roles without duplicating code.

--When including multiple modules in one class, the method of the last included module comes to the beginning of the method search path.

--You can also add module methods to just one object using the Ruby ʻextend keyword. --ʻExtend adds the behavior of the module directly to the object. --Make the class ʻextend` in a module --A class method is added "to that class". --Argument an instance of a class --An instance method is added "to that instance".

7-2. Write inheritable code

--Anti-pattern

  1. A pattern in which an object uses variable names such as type and category to determine what message to send to self. --Common code should be in an abstract superclass and subclasses should be used to create different types.
  2. A pattern in which the object decides which message to send after checking the class of the object that receives the message. --The recipient's object should implement a duck-type interface. --Duck types may share behavior, so in that case, put common code in a module and include that module in each class or object to take on the role.

--There must be no subclasses in the abstract superclass that do not use code. --If you can't narrow down the abstraction to one correctly, or if there is no common code that can be abstracted, inheritance cannot solve the design problem.

--Subclasses promise to replace superclasses. --It is not permissible to let other objects identify their type and decide what to do with them or what to expect.

Liskov Substitution Principle

-"For the system to be healthy, the derived type must be replaceable with the supertype." --In other words, like Ruby, "objects should behave as they claim."

--Use template method pattern --In the concrete inheritance of abstraction, specialization is performed by overriding the templated method. --By using the template method, you have to clearly decide what changes and what does not.

--Avoid writing code that calls super on the inheriting side. --Use hook messages instead. --You can join the algorithm while being freed from the responsibility of knowing the algorithm of the abstract class.

8. Combine objects in composition

8-1. Compose the bike from the parts

--Having the relationship "Bicycle has-a Parts" = Composition --Create an abstract Parts class.

8-2. Compose Parts object

--Make a part. Parts has multiple Part objects. --Group individual Part objects into Parts objects. --Each of the array of Part objects is an object that plays the role of Part. --Not an instance of the Part class. --It is not the type of Part class.

8-3. Manufacture Parts

-"Factory" --An object that creates other objects. --Ruby's OpenStruct class --A convenient way to combine multiple attributes into one object. - Struct --It is necessary to specify the order and pass the arguments at the time of initialization. - OpenStruct --Take a hash at initialization and extract attributes from it.

8-4. Composed Bicycle

--Parts play the role of Parts --OpenStruct takes on the role of Part, which implements name, description, needs_spare.

Aggregation-a special composition

――What is composition? --Two objects have a "has-a" relationship --Things that the contained object cannot exist independently of the contained object ――Example: A dish has an appetizer, but once the dish is eaten, the appetizer disappears as well. ――What is aggregation? --The existence of the contained object is independent. --Example: Even if the university disappears and the faculty disappears, the professors still exist.

8-5. Composition and inheritance choices

--Class inheritance --Instead of paying the cost of organizing objects into a hierarchy, there is no unusual cost for the message. --Composition --Objects can now exist structurally independently, but at the cost of explicit message delegation.

—— Compositions have far fewer dependencies than inheritances, so prioritize compositions.

--Advantages of inheritance --TRUE. --Open / Closed (Open for extensions, closed for fixes)

--Inheritance cost --If you accidentally choose inheritance, you will have to duplicate or reconfigure your code. --May be used by other programmers for completely unexpected purposes.

--Advantages of composition --Because it is structurally independent, it is highly usable and can be easily used even in new contexts that were not expected. ――It is very good for defining the rules for assembling an object consisting of parts.

--Composition cost --Depends on many parts. ――Even if the individual parts are small and easy to understand, the overall operation of the combination is not easy to understand. --Sacrifice the automatic delegation of messages. --You must know explicitly which message to delegate to whom. ――It doesn't help so much for the problem of composing a code with almost the same parts.

--is-a Use inheritance for relationships --Inheritance is a specialization. --Inheritance is best suited for adding functionality to an existing class while using most of the old code and adding a relatively small amount of new code.

--Behaves-like-a Use duck type for relationships --When an object has some role, but that role is not the main responsibility of the object. --When objects that are not related to each other share the desire to play the same role. --Common behavior is defined in Ruby modules so that objects can take on roles without duplicating code.

--has-a Use composition for relationships --When many objects contain a number of parts and the sum of those objects exceeds the sum of those parts. --The more parts an object has, the more likely it is that it should be modeled in a composition.

――Learning to be able to properly use techniques such as composition, inheritance by class, and sharing of behavior using modules is a matter of experience and judgment.

Test design

9. Design a cost-effective test

――Three skills are indispensable for practicing modifiable code.

  1. Understanding object-oriented design.
  2. Good at code refactoring. ――Refactoring refers to the work of improving the internal structure while maintaining the external behavior of the software.
  3. Ability to write high-value tests.

9-1. Intentional testing

--Test intent --Find a bug --It becomes a specification --Delay design decisions --The test proves that the interface continues to behave correctly, so changes to the underlying code do not require you to rewrite the test. --Supporting abstraction --Clarify design flaws

--Write less tests --You should write the most stable, public interface-defined message.

9-2. Test incoming messages

--Remove tests for incoming messages that are not dependent.

--Make a test double. --A fake object that plays a role. A stylized instance of the role bearer. Only used for testing. --Double "stubs" the method. That is, implement a method that returns a pre-filled answer.

9-3. Test private methods

--Private methods are redundant and unstable (variable), and maintenance time will increase, so ignore them.

--Do not create the private method itself in the first place. ――Objects that have a lot of private methods give off the smell of a design that has too much responsibility. --Consider cutting those objects into new objects.

-"Never write private methods. If you do, never test them, unless, of course, it makes sense to do so."

9-4. Test outgoing messages

--The outgoing message is either a "query" or a "command". --Query messages only matter to the objects that send them. --Command messages have visible effects on other objects in your application.

--The test should ignore the message sent to self. --A query message that goes out should also be ignored.

--If you have injected dependent objects in advance, you can easily replace them with mock. --By setting expectations on the mock, you can prove that the object under test fulfills its responsibilities without duplicating statements that belong anywhere else.

9-5. Test the duck type

--If you come across code that uses anti-patterns but doesn't have tests, consider refactoring it before writing the tests for a better design.

--The role test should be written only once and shared by all bearers. --Minitest supports test sharing with Ruby modules. --Cut out the behavior into modules.

--If you treat the test double like any other bearer of the role and prove its correctness in the test, you can avoid the fragility of the test and stub without worrying about the impact.

--The desire to do duck-type testing has created the need for sharable testing for roles. And once you have a perspective based on this role, you can use it in a variety of situations. From the point of view of the object under test, all other objects are roles. Then, treating the object as if it were a representation of that role loosens the bond and increases flexibility. This is true for both applications and testing.

9-6. Test the inherited code

--Liskov Substitution Principle --The derived type should be replaceable with its superordinate type.

--The easiest way to prove that you are following Liskov's substitution principle is to write a test (of the interface) shared in that common contract and include the test of all objects (in the test of the concrete class). ..

--Write a test of behavior common to subclasses.

--By testing the interface of abstract classes and testing the behavior common to subclasses, you can be confident that the subclasses are not out of the standard, and new members will be able to create subclasses quite safely. New programmers don't have to hunt for superclasses to dig up their requirements. When drawing a new subclass, simply include these tests.

--Creating an instance of an abstract class is either difficult, fairly difficult, or impossible.

--When testing subclass-specific areas, it is important not to embed superclass knowledge in the test.

--If the superclass uses template methods to gain concrete specialization, you can usually stub the behavior provided by the subclass. --The idea of creating a subclass to prepare a stub is useful in many situations, and you can use this technique in any test as long as you don't break Liskov's substitution principle.

--Carefully written inheritance structure testing is easy. Write one sharable test for the entire interface and the other for subclass responsibilities. ――Separate responsibilities one by one. --When testing subclass specialization, be careful not to leak your superclass knowledge to the subclass test. --It's difficult to test an abstract superclass. --When using Liskov's substitution principle and creating test-only subclasses, apply the subclass's responsibility test to those subclasses as well.

--The best tests are loosely coupled with the code in question, tested only once for everything, and done in the right place.

Recommended Posts

Read "Object-Oriented Design Practice Guide"
I read the "Object-Oriented Practical Guide", so a memorandum
Read design patterns in Ruby