[システム設計]疎結合にするための工夫4選
はじめに
システム開発系の仕事をしている方なら分かると思いますが、見やすい・保守のしやすいコードを書きたくなりますよね。そもそも、チーム開発をしていると汚いコードを書くこと自体ギルティになってきます(汚いコード書くとめっちゃ怒られます)。
そこで、見やすい・保守性の高いコードを書く上で大切な"疎結合"という考え方(および具体例)について纏めてみました。
疎結合とは
システム開発における「疎結合」(Loose Coupling)とは、システムの各部分が他の部分に対して最小限の依存性を持つ設計のことを指します。これは、各部分がそれぞれ独立して機能し、他の部分の変更に対して影響を受けにくい状態を意味します。
そもそも、疎結合には、以下のような利点を持ちます:
-
変更の容易さ:各部分が独立しているため、一部を変更しても他の部分に影響を与えにくい。これにより、システムのメンテナンスや拡張が容易になります。
-
再利用性:各部分が独立しているため、特定の部分を他のシステムやコンテキストで再利用することが容易になります。
-
テストの容易さ:各部分が独立しているため、個々の部分を単体でテストすることが容易になります。
一方、各部分が独立しているため、全体としての統一感や一貫性を保つことが難しくなる場合もあり、このあたりは注意が必要です。
疎結合にするための工夫4選
Pythonでクラス同士を疎結合にする方法を具体的なコードを使って説明します。
- 依存性注入(Dependency Injection)
依存性注入は、クラスが他のクラスに依存する場合に、その依存性を外部から注入することでクラス間の結合度を下げるテクニックです。
class Engine:
def start(self):
return "Engine started"
class Car:
def __init__(self, engine):
self.engine = engine
def start(self):
return self.engine.start()
engine = Engine()
car = Car(engine)
print(car.start()) # Engine started
この例では、Car
クラスはEngine
クラスに依存していますが、その依存性はCar
のコンストラクタによって注入されます。これにより、Car
クラスは特定のEngine
の実装ではなく、Engine
のインターフェース(start
メソッドを持つ任意のオブジェクト)に依存するようになります。
- 抽象基本クラスの使用
Pythonではインターフェースは直接サポートされていませんが、抽象基本クラス(ABC)を使用して同様の効果を得ることができます。
from abc import ABC, abstractmethod
class AbstractEngine(ABC):
@abstractmethod
def start(self):
pass
class Engine(AbstractEngine):
def start(self):
return "Engine started"
class Car:
def __init__(self, engine):
if not isinstance(engine, AbstractEngine):
raise ValueError("engine must be an instance of AbstractEngine")
self.engine = engine
def start(self):
return self.engine.start()
engine = Engine()
car = Car(engine)
print(car.start()) # Engine started
この例では、Car
クラスはAbstractEngine
インターフェースに依存しています。これにより、Car
クラスは具体的なEngine
クラスではなく、start
メソッドを持つ任意のオブジェクトに依存するようになります。
- イベント駆動プログラミング
イベント駆動プログラミングは、プログラムがユーザーのアクション(クリックやキーボード入力など)やシステムのイベント(タイマーの終了、新しいデータの到着など)に反応して動作するような設計パターンを指します。
Pythonでの具体的な例としては、GUI(Graphical User Interface)ライブラリの一つであるTkinterを使用した簡単なアプリケーションを考えてみましょう。以下のコードは、ボタンをクリックするとメッセージが表示される簡単なGUIアプリケーションです。
import tkinter as tk
def on_button_click():
print("Button clicked!")
root = tk.Tk()
button = tk.Button(root, text="Click me!", command=on_button_click)
button.pack()
root.mainloop()
このコードでは、on_button_click
関数がイベントハンドラとして定義されています。この関数は、ボタンがクリックされたというイベントが発生したときに呼び出されます。command=on_button_click
の部分で、ボタンのクリックイベントに対してこの関数を関連付けています。
root.mainloop()
は、イベントループを開始します。このループは、新しいイベントが発生するのを待ち、イベントが発生すると対応するイベントハンドラを呼び出します。このように、プログラムはイベントが発生するまで待機し、イベントが発生したときに特定のアクションを実行する、というのがイベント駆動プログラミングの基本的な考え方です。
- メディエーターパターン
メディエーターパターンは、ソフトウェア設計パターンの一つで、オブジェクト間の通信をカプセル化し、オブジェクトが直接通信しないようにすることで、オブジェクト間の結合度を低減することを目指します。
Pythonでメディエーターパターンを実装する例を以下に示します。この例では、チャットルーム(メディエータ)とユーザー(コンポーネント)を模しています。
class ChatRoom:
def __init__(self):
self.users = []
def add_user(self, user):
self.users.append(user)
def send_message(self, message, user):
for u in self.users:
if u != user:
u.receive_message(message)
class User:
def __init__(self, name, chat_room):
self.name = name
self.chat_room = chat_room
self.chat_room.add_user(self)
def send_message(self, message):
print(f"{self.name} says: {message}")
self.chat_room.send_message(message, self)
def receive_message(self, message):
print(f"{self.name} received: {message}")
# Usage
chat_room = ChatRoom()
bob = User("Bob", chat_room)
alice = User("Alice", chat_room)
charlie = User("Charlie", chat_room)
bob.send_message("Hello, Alice!")
alice.send_message("Hi, Bob!")
charlie.send_message("Hi, everyone!")
このコードでは、ChatRoom
クラスがメディエーターの役割を果たし、User
クラスがコンポーネントの役割を果たします。ユーザーはメッセージを送信すると、そのメッセージはチャットルームに送られ、チャットルームがそのメッセージを他の全てのユーザーに転送します。これにより、各ユーザーは他のユーザーと直接通信することなく、メッセージを交換することができます。
これらのテクニックは、クラス間の結合度を下げ、コードの再利用性とテスト容易性を向上させるのに役立ちます。
Discussion