Last active
March 28, 2021 15:49
-
-
Save marclerodrigues/701c54284707c00befba28227eedb0ec to your computer and use it in GitHub Desktop.
A Quick Overview of Object Oriented Design
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Chapter 1 - Object-Oriented Design | |
| 1. The Problem Design Solves | |
| > It is the need for change that makes design matter. | |
| 2. Why Change is Hard | |
| > Parts interact to product behavior (objects), the interaction is made through messages passed between them. | |
| > Object-Oriented Design is about managing dependencies. | |
| > OOD - Set of coding techniques to arrange dependencies so objects can tolerate change. | |
| 3. A Practical Definition of Design | |
| > Every application is a collection of code, the code's arrangement is the design. | |
| > Design is the art of arranging code. | |
| > Composed by two components: | |
| 1. You must not only write code for the feature you plan to delivery today, | |
| 2. You must also create code that is amenable to being changed later. | |
| > Practical Design does not antecipate what will happen to your application, it merely accepts that something will change and that, in the present you cannot know. | |
| > The purpose of design is to allow you to do design later and its primary goal is to reduce the cost of change. | |
| 4. Tools of Design | |
| > Just as a sculptor has chisels and files, an object-oriented designer has tools - principles and patterns. | |
| a. Design Principles | |
| 1. SOLID | |
| > Single Responsability | |
| > Open/Closed | |
| > Liscov Substitution | |
| > Interface Segregation | |
| > Dependecy Inversion | |
| 2. DRY (Don't Repeat Yourself) | |
| 3. LoD (Law of Demeter) | |
| b. Design Patterns | |
| 5. The Act of Design | |
| 6. How Design Fails | |
| > Due the lack of it. | |
| > When the act of design is separated of the act of programming | |
| 7. When to Design | |
| > Novice programmers tend to do no design at all. | |
| > Intermediate programmers tend to do overdesign. | |
| > Experience programmers knows how to apply designs when it really pays off. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Chapter 2 - Designing Classes with Single Responsibility | |
| 1. Designing What Belongs in a Class | |
| a. Grouping methods into classes | |
| > OO Languages methods are defined in classes. | |
| > Design is more the art of preserving changeability than it is the act of achieving perfection. | |
| b. Organizing code to Allow Easy Changes | |
| > Transparent - The consequences of change should be obvious in the code that is changing and in distant code that relies upon it. | |
| > Reasonable - The cost of any change should be proportional to the benefits the change achives | |
| > Usable - Existing code should be usable in new and unexpected contexts | |
| > Exemplary - The code itself should encourage those who change it to perpetuate these qualities. | |
| The first step to create code that is TRUE is to ensure that each class has a single, well-defined responsability. | |
| c. Creating Classes That Have a Single Responsability | |
| > Create initializers who don't depend on parameters order or quantity. | |
| d. Why Single Responsibility Matters | |
| > Applications that are easy to change consist of classes that are easy to reuse. | |
| > Classes with more than one responsibility are difficult to reuse. | |
| > You increase your application's change of breaking if you depend on classes that do too much. | |
| e. Determining If a Class Has a Single Responsibility | |
| > Rephrase every one of its methods as a question, asking the question ought to make sense. | |
| - "Please Mr. Class, ...?" | |
| > Describe the class in one sentence. | |
| - If you use the word "and" in your description your class has more than one responsibility. | |
| - If you use the word "or" your class has more than one responsibility and they aren't even very related. | |
| - Cohesion to describe this concept. | |
| - High cohesive classes has a single responsibility (SRP). | |
| - "A class has responsibilities that fulfill its purpose". | |
| f. Determining When to Make Design Decisions | |
| > Do not be compelled to make design decisions prematurely. | |
| > "What is the future cost of doing nothing today?" | |
| - If the same as the current cost what for more information. | |
| g. Writing Code That Embraces Change | |
| > Depend on Behavior, Not Data | |
| - Don't Repeat Yourself (DRY) | |
| - Dry code tolerate change because any change in behavior can be made by changing code in just one place. | |
| 1. Hide Instance Variables | |
| - Always wrap instace variables in accessor methods instead of directly referring to variables. | |
| - Hide the variables even from the classes that defines them. | |
| - By wrapping you create just one place that knows what the instance variables mean, thus if you need to change you're going to change in only one place. | |
| - Send messages to access variables, even if you think them as data. | |
| 2. Hide Data Structures | |
| - Depending on complicated data structure is terrible. | |
| - If you can control the input pass a useful object, if you're compelled to take a messy structure, hide the mess with a normalizing method with Struct. | |
| - Use `Struct` to hide complex data Structures. | |
| ex: | |
| class RRef | |
| attr_reader :wheels | |
| def init(data), do: @wheels = wheelify(data) end | |
| Wheel = Struct.new(:param1, :param2) | |
| def wheelify(data), do: data.collect { |cell| Wheel.new(cell[0], cell[2]) } end | |
| end | |
| h. Enforce Single Responsibility Everywhere | |
| - SRP can also be use in other places like methods. | |
| a. Extra Extra Resposibilities From Methods | |
| - Use the same rules to discover if a class has more than one responsibility question what it does and describe it with one sentence. | |
| - Separating iteration from the action that is being performed on each element is a common case of multiple responsibility. | |
| - Qualities of methods with single responsibility | |
| 1. Expose previously hidden qualities - Makes easier to see what the class does, and maybe remove other responsibilities to another class. | |
| 2. Avoid the need for comments - If a bit of code needs a comment extract that bit to a method. The method name serves the same purpose of the old comment. | |
| 3. Encourage reuse - Other will reuse the methods instead of duplicating code. They will follow the pattern and create small and reusable methods. | |
| 4. Are easy to move to another class - You can rearrange behavior without doing a lot of method extracting and refactoring. | |
| b. Isolate Extra Responsibility in Classes | |
| - Isolate the extra responsibility using a struct with a block to postpone the design decision. | |
| - Or just create another class to |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Chapter 3 - Managing Dependencies | |
| - Inevitably your objects will have to talk to another objects. | |
| - For any desired behavior, an object either knows it personally, inherits it, or knows another objects who knows it. | |
| - To collaborate an object must know something about other, Knowing creates a dependency. | |
| 1. Understanding Dependencies | |
| - An object depends on another object if, when one object changes, the other might be forced to change in turn. | |
| a. Recognizing Dependencies | |
| - An object has a dependency when it knows: | |
| > The name of another class. | |
| > The name of a message that it intends to send to someone other than self. | |
| > The argument that a message requires. | |
| > The order of those arguments. | |
| - Some degree of dependency its necessary after all objects must collaborate, but remove unnecessary. | |
| - Unnecessary dependencies makes code less reasonable. | |
| - Your challenge is to manage dependencies so that each class has the fewest possible. | |
| b. Coupling Between Objects (CBO) | |
| - The more tightly couple two objects are, the more they behave like a single entity. | |
| - A day will come where is easier to rewrite everything than to change anything. | |
| c. Other Dependencies | |
| - Messages chained together to reach behavior that lives in a distant object. | |
| - A LoD (Law of Demeter) violation. | |
| - Tests who depend on code (tightly coupled). | |
| 2. Writing Loosely Coupled Code | |
| a. Inject Dependencies | |
| - Pass as argument an object that responds to the desired method rather than referencing the class itself (dependency injection). | |
| b. Isolate Dependencies | |
| - If you cannot remove unnecessary dependencies you must isolate them like we did in chapter 2. | |
| - Depencies represents vulnerabilities, and they should be concise, explicit, and isolated. | |
| c. Isolate Instance Creation | |
| - Move the dependency instantiation to class initialization, or | |
| - Create its own method to lazily create a new instance of the dependency using a memoize. | |
| - Classes where dependency on class names are concise, explicit and isolated can easily adapt to new requirements. | |
| d. Isolate Vulnerable External Messages | |
| - Messages sent to someone other than self. | |
| - Encapsulate in a method of its own, ex: def method dependency.method end | |
| 3. Remove Argument-Order Dependencies | |
| a. Use Hashes for Initialization Arguments | |
| - Change the code to accept a hash of options instead of fixed list of parameters. | |
| - This let us free to add or remove initialization arguments and defaults. | |
| - Adds Verbosity by The keys names explicit documentation about the arguments. | |
| b. Explicitly Define Defaults | |
| - Use the || operator to set defaults or we can use .fetch for the same purpose. | |
| - Can isolated the defaults in a `defaults` method which returns a hash and merge in the initialize method with the args. | |
| c. Isolating Multiparameter Initialization | |
| - When you cannot control the parameter use a `factory` object to manipulate this circunstance. | |
| - You can do so by creating a module wrapper that the sole purpose is to create new instances of some object. | |
| - An object whose purpose is to create another object is a factory. | |
| - Protect yourself by wrapping each dependency in a method owned by your application. | |
| 4. Managing Dependency Injection | |
| - Dependencies always have a direction, a way to manage them is by reverse that direction. | |
| a. Reversing Dependencies | |
| - If class A depends on B, make B depends on A. | |
| - The choices you made about the direction of the dependencies have far reaching consequences that manifest themselves for the rest of your application. | |
| 1. Choosing Dependency Direction | |
| > Depende on things that change less often than you do. | |
| > Some classes are more likely than other to have change in it requirements. | |
| > Concrete classes are more likely to change the Abstract Classes. | |
| - Concrete is the default. This means that the class can have (direct) instances. In contrast, abstract means that a class cannot have its own (direct) instances. Abstract classes exist purely to generalize common behaviour that would otherwise be duplicated across (sub)classes. | |
| > Changing a class that has many dependencies will result in widespread consequences. | |
| a. Undestading Likelihood of Change | |
| b. Recognizing Concretions and Abstractions | |
| - Abstract - Disassociated from any specific instance. | |
| c. Avoiding Dependent-Laden Classes | |
| d. Finding The Dependencies That Matter |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Chapter 4 - Creating Flexible Interfaces | |
| - Design must be concerned with the messages that pass between objects. | |
| - The conversation between objects takes place, using their interfaces. | |
| 1. Understanding Interfaces | |
| - Interfaces represents a set of messages where the messages themselves define the interface. | |
| 2. Defining Interfaces | |
| - Think a public interface as a menu of a restaurant where the kitchen doesn't expose all the things it does. | |
| 3. Public Interfaces | |
| - Reveal its primary responsability. | |
| - Are expected to be invoked by others. | |
| - Will not change on a whim. | |
| - Are safe for others to depend on. | |
| - Are thoroughly documented in the tests. | |
| 4. Private Interfaces | |
| - Handle implementation details. | |
| - Are not expected to be sent by others objects. | |
| - Can change for any reason whatsoever. | |
| - Are unsafe for others to depend on. | |
| - May not even be referenced in the tests. | |
| 5. Responsibilities, Dependencies and Interfaces | |
| - Depend on things that change less often than you also apply to methods within a class. | |
| 6. Finding the Public Interface | |
| 1. Using Sequence Diagrams | |
| - Make questions about the responsibility of the methods and classes to find out more about the design. | |
| - Explicitly specify the messages that pass between objects, and because objects should only communicate using public interfaces, sequence diagrams are a vehicle for exposing, experimenting with, and ultimately defining those interfaces. | |
| - Change the question from "I know I need this class, what should I do?" to "I need to send this message, who should respond to it?" | |
| - You don't send messages because you have objects, you have objects because you send messages. | |
| 2. Asking for "What" Instead of Telling "How" | |
| - Instead of know too much about some process, you delegate to the responsible class and ask what you what instead of telling how you want some class to behave. | |
| 3. Seeking Context Independence | |
| - The context that an object expects has a direct effect on how difficult it is to reuse. | |
| - Simple Context - Easy to Test / Complex Conext - Hard to Test | |
| - Collaborate with others without knowing who they are. (Dependency Injection) | |
| - You can take advantage of 2.6.2 (What and How) [By Delegating Functions and passing your class (self) as argument] | |
| 4. Trusting Other Objects | |
| - "I know what I want and I trust you to do your part". | |
| - Allow objects to collaborate without binding themselves to context. | |
| 6. Using Messages to Discover Objects | |
| - You can use messages sent to the wrong receivers to discover new objects to make this link. | |
| 7. Creating a Message Based Application | |
| - Switching your attention from objects to messages allows you to concentrate on designing an application build upon public interfaces. | |
| 7. Writing Code That Puts Its Best (Inter)Face Forward | |
| - Clarity of your classes reveal your design skills and reflects your self-discipline. | |
| a. Creating Explicit Interfaces | |
| - Methods in the public interface | |
| > Be explicitly identified as such | |
| > Be more about What than How | |
| > Have names that will not change | |
| > Take a hash as an options parameter | |
| - Method in the private interface | |
| > Be just as intentional about your private interface | |
| > Either do not test private methods or segregate this tests from the public ones. | |
| b. Honor the Public Interfaces of Others | |
| - Only interact with classes using their public interfaces | |
| - Do not depend on private interfaces | |
| - Exercise caution when depending on private interfaces | |
| c. Minimize Context | |
| - Construct public interfaces minimizing the context they require. (What vs How) | |
| - Allow senders to get what they want without knowing how your class implements its behavior. | |
| - Do not succumb to a class with ill-defined or absent public interface. Create one yourself. | |
| - Can be a new method or a wrapper, or a single wrapping class. | |
| 8 - The Law of Demeter (LoD) | |
| - Coding rules that result in loosely coupled objects. | |
| - Some Demeter violations are harmless, but other expose a failure to correctly identify and define public interfaces. | |
| a. Defining Demeter | |
| - "only talk to your immediate neighbors" or "use only one dot". | |
| - Retricts the set of objects a method may send messages. | |
| - It's about types and distant behavior not the number of dots. | |
| b. Consequence of Violations | |
| - Makes your code not TRUE, hard to maintain. | |
| c. Avoiding Violations | |
| - Use delegation to avoid the dots. | |
| - Delegate a message means to pass it to another object often via a wrapper method. | |
| - delegate.rb and forwardable.rb | |
| - Be careful usin delegation to hide tight coupling is not the same as decoupling the code. Your not listing to the spirit of the law your just following the letter. | |
| d. Listening to Demeter | |
| - Demeter isn't telling you to use more delegation. | |
| - Coupling raises the risk to force your class to change. | |
| - When use a chain of objects it binds itself to a very specific implementation and it cannot be reuse in other context. | |
| - Train wrecks are clues that there are objects whose public interfaces are lacking. | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Chapter 5 - Reducing Costs with Duck Typing | |
| - OOD purpose is to reduce the cost of change. | |
| - Duck types are public interfaces that are not tied to any specific class. | |
| - Duck type objects are chamaleons that are defined more by their behaviour than their class. | |
| - "If an objects quacks like a duck and walks like a duck, then its class is immaterial, it's a duck". | |
| 1. Understanding Duck Typing | |
| - Expectations about the behavior of an object come in form of beliefs about its public interface. | |
| - You're not limited to expect an object to respond just to one interface. | |
| - Users of an object should not be concerned about its class. | |
| - It's not what an object is that matters is what it does. | |
| 2. Finding the Duck | |
| - Don't care about the classes, care about the message your method is trying to send. | |
| - Find your Duck, and make all your dependencies respond to a common public interface. | |
| 3. Consequences of Duck Typing | |
| - Once discovered the duck you can elicit new behavior from your application without changing any existing code. | |
| - Concrete code is easy to understand but costly to change. | |
| - Abstract code may seem more obscure but, once understood, far easier to change. | |
| - Treat your objects as if they are defined by their behavior rather than by their class. | |
| - Polymorphism | |
| > Is the state of having many forms. | |
| > in OOP, refers to the ability of many different objects to respond to the same message. | |
| > Senders need not care about the class of the receiver; receivers supply their own specific version of behavior. | |
| > There are a number of ways of achieving polymorphism like duck typing, inheritance and behavior sharing via `modules`. | |
| 4. Writing Code That Relies On Ducks | |
| a. Recognizing Hidden Ducks | |
| - Case statements that switch on class | |
| - kind_of? and is_a? | |
| - responds_to? | |
| 1. Case Statements That Switch on Class | |
| - Find out what message your method wants and them make it avaiable in a public interface of a duck type. | |
| 2. kind_of? and is_a? | |
| - It is the same as case statements that switch on class, thus must be solved by the same techniques. | |
| 3. responds_to? | |
| b. Placing Trust in Your Ducks | |
| c. Documenting Duck Types | |
| - Documented through tests. | |
| d. Sharing Code Between Ducks | |
| e. Choosing your Ducks Wisely |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Chapter 6 - Acquiring Behavior Through Inheritance | |
| 1. Understanding Classical Inheritance | |
| - It's a mechanism for automatic message delegation. | |
| - It defines a forward path for not-understood messages. | |
| - It creates a relationship such that, if one object cannot respond to a received message, it delegates that message to another object. | |
| - You define a inheritance relationship between two objects and forwarding happens automatically. | |
| - Classical Inheritance is defined by creating subclasses. | |
| - Messages are forwarded from subclass to superclass, the shared code is defined in the hierarchy. | |
| 2. Recognizing Where to Use Inheritance | |
| a. Embedding Multiple Types | |
| - Code contains a conditional to check an attribute the hold a classification of self. | |
| - This patterns indicates a missing subtype, better known as a subclass. | |
| b. Finding the Embedded Types | |
| - Variables with names like style, type, category indicates an underlying pattern. Type or Category are words similiar do Class. | |
| - This variables divide instances of the class, into different kind of things. That share a great deal of behavior but differ along some dimension. | |
| - A single class contains several different but related types. | |
| - This is the exact problem inheritance solves, highly related types that share common behavior but differ along some dimension. | |
| c. Choosing Inheritance | |
| - Object either responds directl to a message sent or it passes the message on to some other object for a response. | |
| - Provides a way to define two objects as having a relationship such that when the first receives a message it does not understand, it automatically forwards, or delegates, the message to the second. | |
| - Messages forwarding via classical inheritance takes place between classes. Because duck types cut across classes, they do not use classical inheritance to share common behavior. They do that via Modules. | |
| - Subclasses are thus specializations of their super classes. | |
| d. Drawing Inheritance Relationships | |
| - Use class diagrams to illustrate class relationship. | |
| 3. Missapplying Inheritance | |
| 4. Finding the Abstraction | |
| - Check your classes names to see if you've the right abstraction. | |
| - For inheritance to work your objects must truly have a generalization-specialization relationship. | |
| - You must use the correct coding techniques. | |
| a. Creating an Abstract Superclass | |
| - Abstract classes are disassociated from any specific instance so you will never send the new message. | |
| - They provide a common repository for behavior that will be shared across a set of subclasses - subclasses in turn supply specialization. | |
| b. Promoting Abstract Behavior | |
| - Promote abstraction than demote constructions | |
| c. Separating Abstract from Concrete | |
| d. Using a Template Method Pattern | |
| - Defining a basic structure in the superclass and sending messages to acquire subclass-specific contributions is known as the template method pattern. | |
| e. Implementing Every Template Method | |
| - Every class that implements the template method must supply an implementation for every message it sends, even if the only reasonable implementation is raise a NotImplementedError. | |
| - Always document template methods requirements by implementing matching methods that raise useful errors. | |
| 5. Managing Coupling Between Superclasses and Subclasses | |
| a. Understanding Coupling | |
| - By sending super message your subclasses are depending knwo from superclass implementation, and this introduces dependencies. Which makes code very coupled. The subclasses depended on the knowledge of the algorithm of superclass, if the algorithm changes the subclasses may break. | |
| b. Decoupling Subclasses Using Hook Messages | |
| - You can use hook messages (send methods do the super class) so they can collaborate together. | |
| - Ex: Implement a post_initialize(args) method, to allow subclasses to contribute with the initialization. | |
| def post_initialize(args) nil end | |
| - This change doesn't just remove the need to send super message but it also may remove the initialize method altogether. | |
| - Your subclasses no longer control initialization but now they contribute to it. | |
| - This technique can be used to remove all super messages, by defining methods and then overriding them in subclasses. | |
| - After doing this your subclasses will be more readable since they only contain specializations. | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment