[PYTHON] [Refactoring Catalog] Change from reference to value

Book Refactoring-Safely Improve Existing Code, 2nd Edition

Or Web version (although it is the complete version), A complementary Refactoring Catalog is available.

What is this

Sample code for books and catalogs is JavaScript, Here is a sample code for refactoring in Python based on the catalog

I will leave the detailed explanation to the book and show it with a glue like "Let's do this here"

Catalog: Change Reference to Value

https://refactoring.com/catalog/changeReferenceToValue.html

Initial code, JavaScript version


class Product {
  applyDiscount(arg) {this._price.amount -= arg;}

After refactoring


class Product {
  applyDiscount(arg) {
    this._price = new Money(this._price.amount - arg, this._price.currency);
  }

Python version

Initial code


class Product():
  def applyDiscount(self, arg):
    self._price.amount -= arg

However, when I looked at the book to find out how to define _price, I explained it concretely with class Person and class TelephoneNumber, so I changed it to Gununu.

Initial code, JavaScript version, modified


class Person{
  constructor() {
    this._telephoneNumber = new TelephoneNumber();
  }
  get officeAreaCode()    {return this._telephoneNumber.areaCode;}
  set officeAreaCode(arg) {this._telephoneNumber.areaCode = arg;}
  get officeNumber()    {return this._telephoneNumber.number;}
  set officeNumber(arg) {this._telephoneNumber.number = arg;}  
}

class TelephoneNumber{
  get areaCode()    {return this._areaCode;}
  set areaCode(arg) {this._areaCode = arg;}
  get number()    {return this._number;}
  set number(arg) {this._number = arg;} 
}

After refactoring


class Person{
  constructor() {
    this._telephoneNumber = new TelephoneNumber();
  }
  get officeAreaCode()    {return this._telephoneNumber.areaCode;}
  set officeAreaCode(arg) {
    this._telephoneNumber = new TelephoneNumber(arg, this.officeNumber);
  }
  get officeNumber()    {return this._telephoneNumber.number;}
  set officeNumber(arg) {
    this._telephoneNumber = new TelephoneNumber(this.officeAreaCode, arg);
  }
}

class TelephoneNumber{
  constructor(areaCode, number) {
    this._areaCode = areaCode;
    this._number = number;
  }
  get areaCode()    {return this._areaCode;}
  set areaCode(arg) {this._areaCode = arg;}
  get number()    {return this._number;}
  set number(arg) {this._number = arg;} 
}

Python version, modified

Initial code


class Person():
  def __init__(self):
    self.__telephoneNumber = TelephoneNumber()

  @property
  def officeAreaCode(self):
    return self.__telephoneNumber.areaCode
  @officeAreaCode.setter
  def officeAreaCode(self, arg):
    self.__telephoneNumber.areaCode = arg

  @property
  def officeNumber(self):
    return self.__telephoneNumber.number
  @officeNumber.setter
  def officeNumber(self, arg):
    self.__telephoneNumber.number = arg

class TelephoneNumber():
  def __init__(self):
    self.__areaCode = ''
    self.__number = ''

  @property
  def areaCode(self):
    return self.__areaCode
  @areaCode.setter
  def areaCode(self, arg):
    self.__areaCode = arg

  @property
  def number(self):
    return self.__number
  @number.setter
  def number(self, arg):
    self.__number = arg

Test code

Test code


from unittest import TestCase
class TestPerson(TestCase):
  target = Person()

  def test_officeAreaCode(self):
    self.target.officeAreaCode = '312'
    self.assertEqual(self.target.officeAreaCode, '312')

  def test_officeNumber(self):
    self.target.officeNumber = '555-0142'
    self.assertEqual(self.target.officeNumber, '555-0142')

class TestTelephoneNumber(TestCase):
  target = TelephoneNumber()

  def test_areaCode(self):
    self.target.areaCode = '312'
    self.assertEqual(self.target.areaCode, '312')

  def test_number(self):
    self.target.number = '555-0142'
    self.assertEqual(self.target.number, '555-0142')

Let's go here

Preparing to remove setters, adding parameters to constructor, adding tests


class TestTelephoneNumber(TestCase):
  target = TelephoneNumber()

  def test_areaCode(self):
    self.target.areaCode = '312'
    self.assertEqual(self.target.areaCode, '312')

  def test_number(self):
    self.target.number = '555-0142'
    self.assertEqual(self.target.number, '555-0142')
  
  def test_telephoneNumber(self): # add
    target = TelephoneNumber('312', '555-0142') # add
    self.assertEqual(target.areaCode, '312') # add
    self.assertEqual(target.number, '555-0142') # add

Prepare for setter removal, extract variables


class TelephoneNumber():
  def __init__(self, areaCode='', number=''): # edit
    self.__areaCode = areaCode # edit
    self.__number = number # edit

  @property
  def areaCode(self):
    return self.__areaCode
  @areaCode.setter
  def areaCode(self, arg):
    self.__areaCode = arg

  @property
  def number(self):
    return self.__number
  @number.setter
  def number(self, arg):
    self.__number = arg

Change from reference to value


class Person():
  def __init__(self):
      self.__telephoneNumber = TelephoneNumber()

  @property
  def officeAreaCode(self):
    return self.__telephoneNumber.areaCode
  @officeAreaCode.setter
  def officeAreaCode(self, arg):
    self.__telephoneNumber = TelephoneNumber(arg, self.officeNumber) # edit

  @property
  def officeNumber(self):
    return self.__telephoneNumber.number
  @officeNumber.setter
  def officeNumber(self, arg):
    self.__telephoneNumber = TelephoneNumber(self.officeAreaCode, arg) # edit

Remove setter, remove test


class TestTelephoneNumber(TestCase):
  target = TelephoneNumber('312', '555-0142') # edit

  def test_areaCode(self):
    # self.target.areaCode = '312' # del
    self.assertEqual(self.target.areaCode, '312')

  def test_number(self):
    # self.target.number = '555-0142' # del
    self.assertEqual(self.target.number, '555-0142')
  
  # def test_telephoneNumber(self): # del
  #   target = TelephoneNumber('312', '555-0142') # del
  #   self.assertEqual(target.areaCode, '312') # del
  #   self.assertEqual(target.number, '555-0142') # del

Removal of setter


class TelephoneNumber():
  def __init__(self, areaCode='', number=''): # edit
    self.__areaCode = areaCode # edit
    self.__number = number # edit

  @property
  def areaCode(self):
    return self.__areaCode
  # @areaCode.setter # del
  # def areaCode(self, arg): # del
  #   self.__areaCode = arg # del

  @property
  def number(self):
    return self.__number
  # @number.setter # del
  # def number(self, arg): # del
  #   self.__number = arg # del

TestTelephoneNumber is now immutable, As a value object, I want to be able to judge value-based equivalence, The book is over here, we will also implement it in this article

Equivalence judgment, added test


class TestTelephoneNumber(TestCase):
  target = TelephoneNumber('312', '555-0142')

  def test_areaCode(self):
    self.assertEqual(self.target.areaCode, '312')

  def test_number(self):
    self.assertEqual(self.target.number, '555-0142')
  
  def test_equals(self): # add
    self.assertEqual(self.target == TelephoneNumber('312', '555-0142'), True) # add

Equivalence judgment, first test green


class TelephoneNumber():
  def __init__(self, areaCode='', number=''):
    self.__areaCode = areaCode
    self.__number = number

  def __eq__(self, o): # add
    return True # add

  @property
  def areaCode(self):
    return self.__areaCode
  @areaCode.setter
  def areaCode(self, arg):
    self.__areaCode = arg

  @property
  def number(self):
    return self.__number
  @number.setter
  def number(self, arg):
    self.__number = arg

Equivalence judgment, added test, triangulation


class TestTelephoneNumber(TestCase):
  target = TelephoneNumber('312', '555-0142')

  def test_areaCode(self):
    self.assertEqual(self.target.areaCode, '312')

  def test_number(self):
    self.assertEqual(self.target.number, '555-0142')
  
  def test_equals(self):
    self.assertEqual(self.target == TelephoneNumber('312', '555-0142'), True)

  def test_equals_areaCode_false(self): # add
    self.assertEqual(self.target == TelephoneNumber('312x', '555-0142'), False) # add

Equivalence judgment,__eq__Implemented properly, areaCode


class TelephoneNumber():
  def __init__(self, areaCode='', number=''):
    self.__areaCode = areaCode
    self.__number = number

  def __eq__(self, o):
    return self.areaCode == o.areaCode # edit

  @property
  def areaCode(self):
    return self.__areaCode
  @areaCode.setter
  def areaCode(self, arg):
    self.__areaCode = arg

  @property
  def number(self):
    return self.__number
  @number.setter
  def number(self, arg):
    self.__number = arg

Equivalence judgment, added test, triangulation


class TestTelephoneNumber(TestCase):
  target = TelephoneNumber('312', '555-0142')

  def test_areaCode(self):
    self.assertEqual(self.target.areaCode, '312')

  def test_number(self):
    self.assertEqual(self.target.number, '555-0142')
  
  def test_equals(self):
    self.assertEqual(self.target == TelephoneNumber('312', '555-0142'), True)

  def test_equals_areaCode_false(self):
    self.assertEqual(self.target == TelephoneNumber('312x', '555-0142'), False)

  def test_equals_number_false(self): # add
    self.assertEqual(self.target == TelephoneNumber('312', '555-0142x'), False) # add

Equivalence judgment,__eq__Implemented properly, number


class TelephoneNumber():
  def __init__(self, areaCode='', number=''):
    self.__areaCode = areaCode
    self.__number = number

  def __eq__(self, o):
    return self.areaCode == o.areaCode and self.number == o.number # edit

  @property
  def areaCode(self):
    return self.__areaCode

  @property
  def number(self):
    return self.__number

Classes like this are easier to implement in Python 3.7 and later

dataclass


from dataclasses import dataclass

@dataclass(frozen=True)
class TelephoneNumber():
  areaCode: str = ''
  number: str = ''

It's done

You can rest assured that you can adopt dataclass only by preparing for test green.

that's all

reference

[(Youtube) Say Martin Fowler-Refactoring Catalog-Change from Reference to Value @ Tommy109Give Me to Martin Fowler-Refactoring Catalog-Change from Reference to Value](http://www.youtube. com/watch? v = xUB5e5h6yxw)

Recommended Posts

[Refactoring Catalog] Change from reference to value
[Refactoring Catalog] Change function declaration
Did not change from Python 2 to 3
Sum from 1 to 10
Change the decimal point of logging from, to.
[Python] Change standard input from keyboard to text file
How to change static directory from default in Flask
Send accelerometer value from Eaglet + Intel Edison to Kinesis
I read the Chainer reference (updated from time to time)
[Amazon Linux 2] Change from public key authentication to password authentication
Changes from Python 3.0 to Python 3.5
Changes from Python 2 to Python 3.0
Transition from WSL1 to WSL2
Passing by value, passing by reference, passing by reference,
From editing to execution
Extract the value closest to a value from a Python list element