Artwork

Content provided by iteration podcast, John Jacob, and JP Sio - Web Developers. All podcast content including episodes, graphics, and podcast descriptions are uploaded and provided directly by iteration podcast, John Jacob, and JP Sio - Web Developers or their podcast platform partner. If you believe someone is using your copyrighted work without your permission, you can follow the process outlined here https://ppacc.player.fm/legal.
Player FM - Podcast App
Go offline with the Player FM app!
icon Daily Deals

Single Responsibility

39:14
 
Share
 

Manage episode 222982210 series 1900125
Content provided by iteration podcast, John Jacob, and JP Sio - Web Developers. All podcast content including episodes, graphics, and podcast descriptions are uploaded and provided directly by iteration podcast, John Jacob, and JP Sio - Web Developers or their podcast platform partner. If you believe someone is using your copyrighted work without your permission, you can follow the process outlined here https://ppacc.player.fm/legal.

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_f

    puts '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_f

    puts "\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 Gear class based on the behavior above

see 1_gear.rb

  • Our Gear class has three methods: chainring, cog, and ratio

  • Gear is a subclass of Object and thus inherits many other methods besides the three that we defined

  • What 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 our initialize method

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 Gear class - perhaps it is doing too much
  • We are calculating gear_inches, which is fine - but calculating the tire size seems a little weird

When to Make Design Decisions

  • When we look at the Gear class, there's something off about having rim and tire in there.
  • Right now the code in Gear is transparent and reasonable - this doesn't mean that we have great design. All it means is that we have no dependencies
  • Right now, Gear lies about its responsibilities as it has multiple responsibilities in that it has to do "wheel" calculations in our gear_inches message

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 ratio method does.

  • We can do this by using an attr_reader

    BAD

    def ratio
    @chainring / @cog.to_f
    end

    GOOD

    def ratio
    chainring / cog.to_f
    end

  • If 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 Struct class to wrap a structure

see 4_revealing_references.rb

  • the diameters method now has no knowledge of the internal structure of the array
  • diameters just know that it has to respond to rim and tire and nothing about the data structure
  • Knowledge of the incoming array is encapsulated in our wheelify method

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) }
    end

    def diameter(wheel)
    wheel.rim + (wheel.tire * 2)
    end

  • separating 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 Wheel class from our Gear class

see 5_gear_and_wheel.rb

  continue reading

78 episodes

Artwork

Single Responsibility

iteration

113 subscribers

published

iconShare
 
Manage episode 222982210 series 1900125
Content provided by iteration podcast, John Jacob, and JP Sio - Web Developers. All podcast content including episodes, graphics, and podcast descriptions are uploaded and provided directly by iteration podcast, John Jacob, and JP Sio - Web Developers or their podcast platform partner. If you believe someone is using your copyrighted work without your permission, you can follow the process outlined here https://ppacc.player.fm/legal.

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_f

    puts '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_f

    puts "\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 Gear class based on the behavior above

see 1_gear.rb

  • Our Gear class has three methods: chainring, cog, and ratio

  • Gear is a subclass of Object and thus inherits many other methods besides the three that we defined

  • What 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 our initialize method

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 Gear class - perhaps it is doing too much
  • We are calculating gear_inches, which is fine - but calculating the tire size seems a little weird

When to Make Design Decisions

  • When we look at the Gear class, there's something off about having rim and tire in there.
  • Right now the code in Gear is transparent and reasonable - this doesn't mean that we have great design. All it means is that we have no dependencies
  • Right now, Gear lies about its responsibilities as it has multiple responsibilities in that it has to do "wheel" calculations in our gear_inches message

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 ratio method does.

  • We can do this by using an attr_reader

    BAD

    def ratio
    @chainring / @cog.to_f
    end

    GOOD

    def ratio
    chainring / cog.to_f
    end

  • If 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 Struct class to wrap a structure

see 4_revealing_references.rb

  • the diameters method now has no knowledge of the internal structure of the array
  • diameters just know that it has to respond to rim and tire and nothing about the data structure
  • Knowledge of the incoming array is encapsulated in our wheelify method

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) }
    end

    def diameter(wheel)
    wheel.rim + (wheel.tire * 2)
    end

  • separating 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 Wheel class from our Gear class

see 5_gear_and_wheel.rb

  continue reading

78 episodes

All episodes

×
 
Loading …

Welcome to Player FM!

Player FM is scanning the web for high-quality podcasts for you to enjoy right now. It's the best podcast app and works on Android, iPhone, and the web. Signup to sync subscriptions across devices.

 

icon Daily Deals
icon Daily Deals
icon Daily Deals

Quick Reference Guide

Copyright 2025 | Privacy Policy | Terms of Service | | Copyright
Listen to this show while you explore
Play