I want to read it little by little over a month or two. This is a memo for myself.
--If you follow the object-oriented design method, you can enjoy coding and produce software most efficiently! --Object-oriented design = Managing dependencies. --Parts
--Design principle "SOLID" --Design pattern "GoF"
--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.
--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.
--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.
--The first step to writing TRUE code is to ensure that you have a single responsibility (= minimal and useful).
--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.
--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.
--Types of object behavior
--Dependencies exist when an object knows:
self
--Minimize dependencies so that code changes aren't big enough to force changes to other objects.
--Dependence is evil. --All addictions are like foreign bacteria that try to undermine the class (design).
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)
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
.
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, fetch
Method, merge
Method
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.
--Reverse dependencies --Selection of dependency direction -"Depend on what is unchanged from yourself" = Three facts that are the basis of the following concept
--The key to maintenance is to control the direction of dependency and rely on a class that changes less than you.
--Design is not just about what an object knows (object responsibility) or who it knows (object dependencies). --How the objects talk to each other.
--Public interface --Methods exposed to the outside --The class implements methods, some of which are intended to be used by other objects.
--Characteristics of public interface
--Characteristics of private interface
--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.
--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
――The interface defines the application and determines the future.
--Every time you create a class, declare the interface. --Methods included in the "public" interface
--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.
--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.
――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."
--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.
--A wide variety of objects also refer to the ability to respond to messages. --One message has many (poly) morphs.
--Difficult design ――Aware that you need a duck type --Abstracting that interface
--How to recognize a hidden duck
--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.
--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.
--Inheritance is the mechanism of "automatic delegation of messages"
--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.
--Sending super will inherit unnecessary, totally meaningless behavior.
--Inheritance rules
--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
.
--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".
--Anti-pattern
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.--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.
-"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.
--Having the relationship "Bicycle has-a Parts" = Composition --Create an abstract Parts class.
--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.
-"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.
--Parts play the role of Parts --OpenStruct takes on the role of Part, which implements name, description, needs_spare.
――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.
--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.
――Three skills are indispensable for practicing modifiable code.
--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.
--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.
--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."
--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.
--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.
--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.