انتقل إلى وضع عدم الاتصال باستخدام تطبيق Player FM !
Single Responsibility
Manage episode 222982210 series 1900125
Chapter 2: Designing Classes with a Single Responsibility
The foundation of an object-oriented system is the message, but the most visible organizational structure is the class
Questions to ask yourself:
- What are your classes?
- How many should you have?
- What behavior will they implement?
- How much do they know about other classes?
- How much of themselves should they expose?
Creating Classes That Have a Single Responsibility
A class should do the smallest possible useful thing; that is, it should have a single responsibility
An Example Application: Bicycles and Gears
- Let's take a look at bikes. Consider the types of gears that bikes use
Small Gears
- easy to pedal, not as fast
- takes many pedals just to make the tires rotate once
- can help you creep along steep hills
Large Gears
harder to pedal, fast
sends you flying down those steep hills
one pedal rotation with your foot might cause the tires to rotate multiple times
Let's start with a small script and then extrapolate classes out of it:
Large Gear
chainring = 52
cog = 11
ratio = chainring / cog.to_fputs 'Large Gear:'\
"\n#{chainring}-tooth chainring"\
"\n#{cog}-tooth cog"\
"\n#{ratio.round(2)} rotations"Small Gear
chainring = 30
cog = 27
ratio = chainring / cog.to_fputs "\nSmall Gear:"\
"\n#{chainring}-tooth chainring"\
"\n#{cog}-tooth cog"\
"\n#{ratio.round(2)} rotations"Since we're talking about gears, it only makes sense that we start by creating a
Gearclass based on the behavior above
see 1_gear.rb
Our
Gearclass has three methods:chainring,cog, andratioGearis a subclass ofObjectand thus inherits many other methods besides the three that we definedWhat I'm trying to say is that the complete set of behavior / the total set of messages to which it can respond is fairly large
This is great and all - but what if we want to extend the functionality by taking into account the effect of the difference in wheels
- Bigger wheels travel much farther during each wheel rotation versus smaller wheels
Consider this formula
gear inches = wheel diameter × gear ratio
(where)
wheel diameter = rim diameter + (2 × tire diameter)
see 2_gear.rb
- This new code is great except our old call to
Gear.new(52, 11)no longer works because we added 2 more arguments to ourinitializemethod
Why Single Responsibility matters
- Applications that are easy to change consist of classes that are easy to reuse. [...] A class that has more than one responsibility are difficult to reuse
Determining If a Class Has a Single Responsibility
- How can you tell if your class is only doing a single thing? Try describing what it does in a single sentence. You'll find out very quickly
- Remember that a class should do the smallest possible useful thing
- When we look at our
Gearclass - perhaps it is doing too much - We are calculating
gear_inches, which is fine - but calculating thetiresize seems a little weird
When to Make Design Decisions
- When we look at the
Gearclass, there's something off about havingrimandtirein there. - Right now the code in
Gearis transparent and reasonable - this doesn't mean that we have great design. All it means is that we have no dependencies - Right now,
Gearlies about its responsibilities as it has multiple responsibilities in that it has to do "wheel" calculations in ourgear_inchesmessage
Write Code That Embraces Change
Here are some techniques that help you write code that embraces change
Depend on Behavior, Not Data
- Behavior is captured in methods and invoked by sending messages
- Objects also contain data (not just behavior)
Hide Instance Variables
Always wrap instance variables in accessor methods instead of directly referring to variables, like the
ratiomethod does.We can do this by using an
attr_readerBAD
def ratio
@chainring / @cog.to_f
endGOOD
def ratio
chainring / cog.to_f
endIf your instance variable is referred to multiple times and it suddenly needs to change, you're in for a world of hurt.
Your method that wraps your instance variable becomes the single source of truth
One drawback is that because you can wrap any instance variables in methods, its possible to obfuscate the distinction between data and objects
But the point is that you should be hiding data from yourself.
Hiding data from yourself protects code from unexpected changes
Hide Data Structures
- Depending on a complicated data structure can also lead to a world of hurt
- For instance, if you create a method that expects the data structure is being passed to it to be an array of arrays with two items in each array - you create a dependency
see 3_obscuring_references.rb
- Ruby makes it easy to separate structure from meaning
- You can use a Ruby
Structclass to wrap a structure
see 4_revealing_references.rb
- the
diametersmethod now has no knowledge of the internal structure of the array diametersjust know that it has to respond torimandtireand nothing about the data structure- Knowledge of the incoming array is encapsulated in our
wheelifymethod
Enforce Single Responsibility Everywhere
Extra Extra Responsibilities from Methods
def diameters wheels.collect { |wheel| wheel.rim + (wheel.tire * 2) } end this method clearly has two responsibilities
- iterate over wheels
- calculate the diameter of each wheel
we can separate these into two methods that each have their own responsibility
def diameters
wheels.collect { |wheel| diameter(wheel) }
enddef diameter(wheel)
wheel.rim + (wheel.tire * 2)
endseparating iteration from the action that's being performed on each element is a common case of multiple responsibilities
Finally, the Real Wheel
- New feature request: program should calculate bicycle wheel circumference
- Now we can separate a
Wheelclass from ourGearclass
see 5_gear_and_wheel.rb
78 حلقات
Manage episode 222982210 series 1900125
Chapter 2: Designing Classes with a Single Responsibility
The foundation of an object-oriented system is the message, but the most visible organizational structure is the class
Questions to ask yourself:
- What are your classes?
- How many should you have?
- What behavior will they implement?
- How much do they know about other classes?
- How much of themselves should they expose?
Creating Classes That Have a Single Responsibility
A class should do the smallest possible useful thing; that is, it should have a single responsibility
An Example Application: Bicycles and Gears
- Let's take a look at bikes. Consider the types of gears that bikes use
Small Gears
- easy to pedal, not as fast
- takes many pedals just to make the tires rotate once
- can help you creep along steep hills
Large Gears
harder to pedal, fast
sends you flying down those steep hills
one pedal rotation with your foot might cause the tires to rotate multiple times
Let's start with a small script and then extrapolate classes out of it:
Large Gear
chainring = 52
cog = 11
ratio = chainring / cog.to_fputs 'Large Gear:'\
"\n#{chainring}-tooth chainring"\
"\n#{cog}-tooth cog"\
"\n#{ratio.round(2)} rotations"Small Gear
chainring = 30
cog = 27
ratio = chainring / cog.to_fputs "\nSmall Gear:"\
"\n#{chainring}-tooth chainring"\
"\n#{cog}-tooth cog"\
"\n#{ratio.round(2)} rotations"Since we're talking about gears, it only makes sense that we start by creating a
Gearclass based on the behavior above
see 1_gear.rb
Our
Gearclass has three methods:chainring,cog, andratioGearis a subclass ofObjectand thus inherits many other methods besides the three that we definedWhat I'm trying to say is that the complete set of behavior / the total set of messages to which it can respond is fairly large
This is great and all - but what if we want to extend the functionality by taking into account the effect of the difference in wheels
- Bigger wheels travel much farther during each wheel rotation versus smaller wheels
Consider this formula
gear inches = wheel diameter × gear ratio
(where)
wheel diameter = rim diameter + (2 × tire diameter)
see 2_gear.rb
- This new code is great except our old call to
Gear.new(52, 11)no longer works because we added 2 more arguments to ourinitializemethod
Why Single Responsibility matters
- Applications that are easy to change consist of classes that are easy to reuse. [...] A class that has more than one responsibility are difficult to reuse
Determining If a Class Has a Single Responsibility
- How can you tell if your class is only doing a single thing? Try describing what it does in a single sentence. You'll find out very quickly
- Remember that a class should do the smallest possible useful thing
- When we look at our
Gearclass - perhaps it is doing too much - We are calculating
gear_inches, which is fine - but calculating thetiresize seems a little weird
When to Make Design Decisions
- When we look at the
Gearclass, there's something off about havingrimandtirein there. - Right now the code in
Gearis transparent and reasonable - this doesn't mean that we have great design. All it means is that we have no dependencies - Right now,
Gearlies about its responsibilities as it has multiple responsibilities in that it has to do "wheel" calculations in ourgear_inchesmessage
Write Code That Embraces Change
Here are some techniques that help you write code that embraces change
Depend on Behavior, Not Data
- Behavior is captured in methods and invoked by sending messages
- Objects also contain data (not just behavior)
Hide Instance Variables
Always wrap instance variables in accessor methods instead of directly referring to variables, like the
ratiomethod does.We can do this by using an
attr_readerBAD
def ratio
@chainring / @cog.to_f
endGOOD
def ratio
chainring / cog.to_f
endIf your instance variable is referred to multiple times and it suddenly needs to change, you're in for a world of hurt.
Your method that wraps your instance variable becomes the single source of truth
One drawback is that because you can wrap any instance variables in methods, its possible to obfuscate the distinction between data and objects
But the point is that you should be hiding data from yourself.
Hiding data from yourself protects code from unexpected changes
Hide Data Structures
- Depending on a complicated data structure can also lead to a world of hurt
- For instance, if you create a method that expects the data structure is being passed to it to be an array of arrays with two items in each array - you create a dependency
see 3_obscuring_references.rb
- Ruby makes it easy to separate structure from meaning
- You can use a Ruby
Structclass to wrap a structure
see 4_revealing_references.rb
- the
diametersmethod now has no knowledge of the internal structure of the array diametersjust know that it has to respond torimandtireand nothing about the data structure- Knowledge of the incoming array is encapsulated in our
wheelifymethod
Enforce Single Responsibility Everywhere
Extra Extra Responsibilities from Methods
def diameters wheels.collect { |wheel| wheel.rim + (wheel.tire * 2) } end this method clearly has two responsibilities
- iterate over wheels
- calculate the diameter of each wheel
we can separate these into two methods that each have their own responsibility
def diameters
wheels.collect { |wheel| diameter(wheel) }
enddef diameter(wheel)
wheel.rim + (wheel.tire * 2)
endseparating iteration from the action that's being performed on each element is a common case of multiple responsibilities
Finally, the Real Wheel
- New feature request: program should calculate bicycle wheel circumference
- Now we can separate a
Wheelclass from ourGearclass
see 5_gear_and_wheel.rb
78 حلقات
كل الحلقات
×مرحبًا بك في مشغل أف ام!
يقوم برنامج مشغل أف أم بمسح الويب للحصول على بودكاست عالية الجودة لتستمتع بها الآن. إنه أفضل تطبيق بودكاست ويعمل على أجهزة اندرويد والأيفون والويب. قم بالتسجيل لمزامنة الاشتراكات عبر الأجهزة.