Wydzielenie zduplikowanego kodu a dziedziczenie

Przećwiczmy sobie praktycznie na prostym przykładzie techniki, które zastosujemy w kodzie naszych testów.

Kod do ćwiczeń

Wyobraźmy sobie prosty kod, który powstał po wielu latach żmudnej pracy nad modelem komunikacji urządzeń:

class Device():
   def __init__(self, dev_name):
       self.name = dev_name

   def turn_on(self):
       print(f"Device {self.name} turned on")

   def turn_off(self):
       print(f"Device {self.name} turned off")


class MobilePhone(Device):
   def game_mode(self):
       print(f"Device {self.name} set to game mode")

   def exchange_data(self):
       print(f"Device {self.name} exchanging data")

   def ring(self):
       print(f"Device {self.name} ringing")

   def update(self):
       print(f"Device {self.name} is being updated")


class SmartWatch(Device):
   def update(self):
       print(f"Device {self.name} is being updated")

   def measure_heart_rate(self):
       print(f"Device {self.name} measuring_heart_rate")

   def ring(self):
       print(f"Device {self.name} ringing")

   def exchange_data(self):
       print(f"Device {self.name} exchanging data")
       print(f"Device {self.name} checking connection with mobile phone")


Kod wydaje się nie być skomplikowany. Użyjmy go. Stwórz nowy plik w scratches (Otwieranie widoku Scratches i tworzenie pliku Scratches) i wpisz powyższy kod.

Następnie, poniżej kodu klas, dodajmy proste użycie urządzeń:

# initialising and using devices
x_phone = MobilePhone("xPhone")
x_watch = SmartWatch("xWatch")

x_phone.turn_on()
x_phone.game_mode()
x_phone.exchange_data()
x_phone.ring()
x_phone.update()
x_phone.turn_off()

x_watch.turn_on()
x_watch.update()
x_watch.measure_heart_rate()
x_watch.ring()
x_watch.exchange_data()
x_watch.turn_off()

Wynik jaki otrzymamy na konsolę to:

Device xPhone turned on
Device xPhone set to game mode
Device xPhone exchanging data
Device xPhone ringing
Device xPhone is being updated
Device xPhone turned off
Device xWatch turned on
Device xWatch is being updated
Device xWatch measuring_heart_rate
Device xWatch ringing
Device xWatch exchanging data
Device xWatch checking connection with mobile phone
Device xWatch turned off

Czyli kod działa, nie mniej my chcemy go trochę usprawnić. Popatrz na metodę ring(). Jest ona identyczna w obu klasach MobilePhone i SmartWatch. Zrefaktorujmy więc tę duplikację!

Wyciąganie wspólnej metody do klasy bazowej

Wydzielimy metodę do klasy bazowej czyli użyjemy mechanizmu dziedziczenia. Po takim zabiegu wszystkie dziedziczące klasy będą mogły korzystać z metody zaimplementowanej w klasie bazowej 🙂

Ale jak wyciągnąć tę metodę? Jeśli są one identyczne w różnych klasach to wystarczy, że po prostu wytniemy je z obu klas i wkleimy w klasę bazową. Na celownik wzięliśmy metodę ring() i:

  1. Wycinamy ją z obu klas
  2. Wklejamy ją do klasy bazowej
  3. Uruchamiamy kod i sprawdzamy czy otrzymaliśmy wynik taki sam jak poprzednio – oznacza to, że nasz refaktor się powiódł 😀

Tak będzie wyglądał kod klas po refaktorze:

class Device():
   def __init__(self, dev_name):
       self.name = dev_name

   def turn_on(self):
       print(f"Device {self.name} turned on")
  
   def turn_off(self):
       print(f"Device {self.name} turned off")
  
   def ring(self):
       print(f"Device {self.name} ringing")


class MobilePhone(Device):
   def game_mode(self):
       print(f"Device {self.name} set to game mode")

   def exchange_data(self):
       print(f"Device {self.name} exchanging data")
  
   def update(self):
       print(f"Device {self.name} is being updated")


class SmartWatch(Device):
   def update(self):
       print(f"Device {self.name} is being updated")

   def measure_heart_rate(self):
       print(f"Device {self.name} measuring_heart_rate")
  
   def exchange_data(self):
       print(f"Device {self.name} exchanging data")
       print(f"Device {self.name} checking connection with mobile phone")

Jest nieźle, pozbyliśmy się zduplikowanego kodu i wszystko działa jak poprzednio. Dzięki dziedziczeniu możemy bez problemu dalej wywoływać metodę ring() na obiektach klasy MobilePhone czy SmartWatch.

Zmiana wymagań

Teraz dodamy pewne utrudnienie:

Niestety okazało się, że klasa Device pod żadnym pozorem nie może być zmieniana, gdyż jest to zewnętrzna biblioteka i podlega utrzymaniu i modyfikacji przez inne firmy. Oznacza to, że nie możemy sobie tam tak wklejać metod…

Załóżmy też, że wcześniejsze działania czyli wyciągnięcie metody ring() zrobiliśmy wcześniej jako właściciele klasy Device. Niestety teraz sytuacja się zmieniła i już nie możemy tak działać.

Co zrobić?

2 komentarze

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *