Practical Object Oriented Design - KeynesYouDigIt/Knowledge GitHub Wiki

Designing Classes with a Single Responsibility

Deciding what belongs in a class

It's impossible to get it right up front.

Grouping Methods into Classes

Organizing Code to Allow for Easy Changes

"Easy to change" means:

  • Changes have no unexpected side effects
  • Small changes in requirements == small changes in code
  • Existing code is easy to reuse
  • The easiest way to make a change is to add code that in itself is easy to change

TRUE code:

  • Transparent: Consequences of change should be obvious
  • Reasonable: The cost of a change should be proportional to the benefits of the change
  • 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

Creating Classes That Have a Single Responsibility

An Example Application: Bicycles and Gears

Why Single Responsibility Matters

Determining If a Class Has a Single Responsibility

Writing Code That Embraces Change

Depend on Behavior, Not Data

Enforce Single Responsibility Everywhere

Finally, the Real Wheel

Managing Dependencies

Understanding Dependencies

Recognizing Dependencies

Coupling Between Objects (CBO)

Other Dependencies

Writing Loosely Coupled Code

Inject Dependencies

Isolate Dependencies

Remove Argument-Order Dependencies

Managing Dependency Direction

Reversing Dependencies

Choosing Dependency Direction

Creating Flexible Interfaces

Understanding Interfaces

Defining Interfaces

Public interfaces

Private interfaces

Responsibilites, Dependencies, and Interfaces

Finding the Public Interface

An Example Application: Bicycle Touring Company

Constructing an Intention

Using Sequence Diagrams

Asking for "What" Instead of Telling "How"

Seeking Context Independence

Trusting Other Objects

Using Messages to Discover Objects

Creating a Message-Based Application

Writing Code That Puts Its Best (Inter)Face Forward

Create Explicit Interfaces

Honor the Public Interfaces of Others

Exercise Caution When Depending on Private Interfaces

Minimize Context

The Law of Demeter

Defining Demeter

Consequences of Violations

Avoiding Violations

Listening to Demeter

Reducing Costs with Duck Typing

Understanding Duck Typing

Overlooking the Duck

Compounding the Problem

Finding the Duck

Consequences of Duck Typing

Writing Code That Relies on Ducks

Recognizing Hidden Ducks

Placing Trust in Your Ducks

Documenting Duck Types

Sharing Code between Ducks

Choosing Your Ducks Wisely

Conquering a Fear of Duck Typing

Subverting Duck Types with Static Typing

Static vs. Dynamic Typing

Embracing Dynamic Typing

Acquiring Behavior through Inheritance

Understanding Classical Inheritance

Recognizing Where to Use Inheritance

Starting with a Concrete Class

Embedding Multiple Types

Finding the Embedded Types

Choosing Inheritance

Drawing Inheritance Relationships

Misapplying Inheritance

Finding the Abstraction

Creating an Abstract Superclass

Promoting Abstract Behavior

Separating Abstract from Concrete

Using the Template Method Pattern

Implementing Every Template Method

Managing Coupling between Superclasses and Subclasses

Understanding Coupling

Decoupling Subclasses Using Hook Messages

Sharing Role Behavior with Modules

Understanding Roles

Finding Roles

Organizing Responsibilities

Removing Unnecessary Dependencies

Writing the Concrete Code

Extracting the Abstraction

Looking Up Methods

Inheriting Role Behavior

Writing Inheritable Code

Recognize the Antipatterns

Insist on the Abstraction

Honor the Contract

Use the Template Method Pattern

Preemptively Decouple Classes

Create Shallow Hierarchies

Combining Objects with Composition

Composing a Bicycle of Parts

Updating the Bicycle Class

Creating a Parts Hierarchy

Composing the Parts Objects

Creating a Part

Making the Parts Object More Like an Array

Manufacturing Parts

Creating the PartsFactory

Leveraging the PartsFactory

The Composed Bicycle

Deciding Between Inheritance and Composition

Accepting the Consequences of Inheritance

Accepting the Consequences of Composition

Choosing Relationships

Designing Cost-Effective Tests

Intentional Testing

Knowing Your Intentions

Knowing What to Test

Knowing When to Test

  • Incoming messages should be tested for the state they return
  • Outgoing command messages should be tested to ensure they get sent
  • Outgoing query messages should not be tested

Knowing How to Test

Testing Incoming Messages

Deleting Unused Interfaces

Proving the Public Interface

Isolating the Object under Test

Injecting Dependencies Using Classes

Injecting Dependencies as Roles

Testing Private Methods

Ignoring Private Methods during Tests

Removing Private Methods from the Class under Test

Choosing to Test a Private Method

Testing Outgoing Messages

Ignoring Query Messages

Proving Command Messages

Testing Duck Types

Testing Roles

Using Role Tests to Validate Doubles

Testing Inherited Code

Specifying the Inherited Interface

Specifying Sublass Responsibilities

Testing Unique Behavior