SOLID:オブジェクト指向設計の原則
SOLID は、オブジェクト指向プログラミングにおけるクラス設計の頭字語です。この原則は、優れたプログラミング習慣と保守可能なコードの開発に役立つ慣行を確立します。
コードのメンテナンスと拡張性を長期的に考慮することにより、SOLID の原則はアジャイル コード開発環境を充実させます。コードの依存関係を考慮して最適化することで、より簡単で組織化されたソフトウェア開発ライフサイクルを作成できます。
堅固な原則とは
SOLID は、クラスを設計するための一連の原則を表しています。ロバート C. マーティン (ボブおじさん) は、ほとんどの設計原則を導入し、頭字語を作り出しました。
SOLID は次の略です:
- 単一責任の原則
- オープンクローズの原則
- リスコフ置換原則
- インターフェース分離の原則
- 依存性逆転の原則
SOLID の原則は、ソフトウェア設計のベスト プラクティスをまとめたものです。それぞれのアイデアは設計フレームワークを表しており、プログラミングの習慣を改善し、コード設計を改善し、エラーを減らします。
SOLID:5 つの原則の説明
SOLID の原則がどのように機能するかを理解する最善の方法は、例を使用することです。すべての原則は補完的であり、個々のユース ケースに適用されます。原則が適用される順序は重要ではなく、すべての原則がすべての状況に適用できるわけではありません。
以下の各セクションでは、Python プログラミング言語の各 SOLID 原則の概要を説明します。 SOLID の一般的な考え方は、PHP、Java、C# などのあらゆるオブジェクト指向言語に適用されます。ルールを一般化することで、マイクロサービスなどの最新のプログラミング アプローチに適用できるようになります。
単一責任の原則 (SRP)
単一責任の原則 (SRP) は次のように述べています。
クラスを変更するときは、単一の機能のみを変更する必要があります。これは、すべてのオブジェクトが 1 つのジョブのみを持つ必要があることを意味します。
例として、次のクラスを見てください:
# A class with multiple responsibilities
class Animal:
# Property constructor
def __init__(self, name):
self.name = name
# Property representation
def __repr__(self):
return f'Animal(name="{self.name}")'
# Database management
def save(animal):
print(f'Saved {animal} to the database')
if __name__ == '__main__':
# Property instantiation
a = Animal('Cat')
# Saving property to a database
Animal.save(a)
save()
に変更を加える場合 メソッド、変更は Animal
で発生します クラス。プロパティを変更すると、変更は Animal
でも発生します クラス。
クラスには 2 つの変更理由があり、単一責任の原則に違反しています。コードは期待どおりに機能しますが、設計原則を尊重しないと、長期的にはコードの管理が難しくなります。
単一責任の原則を実装するには、サンプル クラスに 2 つの異なるジョブがあることに注意してください。
- プロパティ管理 (コンストラクタと
get_name()
). - データベース管理
(save()
).
したがって、この問題に対処する最善の方法は、データベース管理メソッドを新しいクラスに分離することです。例:
# A class responsible for property management
class Animal:
def __init__(self, name):
self.name = name
def __repr__(self):
return f'Animal(name="{self.name}")'
# A class responsible for database management
class AnimalDB:
def save(self, animal):
print(f'Saved {animal} to the database')
if __name__ == '__main__':
# Property instantiation
a = Animal('Cat')
# Database instantiation
db = AnimalDB()
# Saving property to a database
db.save(a)
AnimalDB
の変更 クラスは Animal
には影響しません 単一責任の原則が適用されたクラス。コードは直感的で、変更も簡単です。
オープンクローズ原則 (OCP)
オープン/クローズの原則 (OCP) は次のように述べています。
システムに機能とユースケースを追加するために、既存のエンティティを変更する必要はありません。新しい機能を追加するには、既存のコードを変更する必要があります。
この考え方は、次の例で簡単に理解できます:
class Animal:
def __init__(self, name):
self.name = name
def __repr__(self):
return f'Animal(name="{self.name}")'
class Storage:
def save_to_db(self, animal):
print(f'Saved {animal} to the database')
Storage
クラスは Animal
からの情報を保存します インスタンスをデータベースに。 CSV ファイルへの保存などの新しい機能を追加するには、Storage
にコードを追加する必要があります。 クラス:
class Animal:
def __init__(self, name):
self.name = name
def __repr__(self):
return f'Animal(name="{self.name}")'
class Storage:
def save_to_db(self, animal):
print(f'Saved {animal} to the database')
def save_to_csv(self,animal):
printf(f’Saved {animal} to the CSV file’)
save_to_csv
メソッドは既存の Storage
を変更します 機能を追加するクラス。このアプローチは、新しい機能が登場したときに既存の要素を変更することにより、オープン/クローズの原則に違反しています。
このコードでは、汎用の Storage
を削除する必要があります
次のコードは、開閉原理の適用を示しています:
class DB():
def save(self, animal):
print(f'Saved {animal} to the database')
class CSV():
def save(self, animal):
print(f'Saved {animal} to a CSV file')
コードは開閉原則に準拠しています。完全なコードは次のようになります:
class Animal:
def __init__(self, name):
self.name = name
def __repr__(self):
return f'"{self.name}"'
class DB():
def save(self, animal):
print(f'Saved {animal} to the database')
class CSV():
def save(self, animal):
print(f'Saved {animal} to a CSV file')
if __name__ == '__main__':
a = Animal('Cat')
db = DB()
csv = CSV()
db.save(a)
csv.save(a)
追加機能 (XML ファイルへの保存など) で拡張しても、既存のクラスは変更されません。
リスコフ置換原理 (LSP)
Liskov 置換原則 (LSP) は次のように述べています。
この原則では、親クラスは機能に目立った変更を加えることなく子クラスを置き換えることができると述べています。
以下のファイル書き込み例を確認してください:
# Parent class
class FileHandling():
def write_db(self):
return f'Handling DB'
def write_csv(self):
return f'Handling CSV'
# Child classes
class WriteDB(FileHandling):
def write_db(self):
return f'Writing to a DB'
def write_csv(self):
return f"Error: Can't write to CSV, wrong file type."
class WriteCSV(FileHandling):
def write_csv(self):
return f'Writing to a CSV file'
def write_db(self):
return f"Error: Can't write to DB, wrong file type."
if __name__ == "__main__":
# Parent class instantiation and function calls
db = FileHandling()
csv = FileHandling()
print(db.write_db())
print(db.write_csv())
# Children classes instantiations and function calls
db = WriteDB()
csv = WriteCSV()
print(db.write_db())
print(db.write_csv())
print(csv.write_db())
print(csv.write_csv())
親クラス (FileHandling
) は、データベースと CSV ファイルに書き込むための 2 つの方法で構成されます。クラスは両方の機能を処理し、メッセージを返します。
2 つの子クラス (WriteDB
そしてWriteCSV
) 親クラスからプロパティを継承 (FileHandling
)。ただし、不適切な書き込み関数を使用しようとすると、両方の子がエラーをスローします。これは、オーバーライド関数が親関数に対応していないため、リスコフ置換の原則に違反します。
次のコードは問題を解決します:
# Parent class
class FileHandling():
def write(self):
return f'Handling file'
# Child classes
class WriteDB(FileHandling):
def write(self):
return f'Writing to a DB'
class WriteCSV(FileHandling):
def write(self):
return f'Writing to a CSV file'
if __name__ == "__main__":
# Parent class instantiation and function calls
db = FileHandling()
csv = FileHandling()
print(db.write())
print(csv.write())
# Children classes instantiations and function calls
db = WriteDB()
csv = WriteCSV()
print(db.write())
print(csv.write())
子クラスは親関数に正しく対応します。
インターフェース分離の原則 (ISP)
インターフェイス分離の原則 (ISP) は次のように述べています。「多くのクライアント固有のインターフェイスは、1 つの汎用インターフェイスよりも優れています。」
言い換えれば、より広範なインタラクション インターフェイスは、より小さなインターフェイスに分割されます。この原則により、クラスは必要なメソッドのみを使用することが保証され、全体的な冗長性が削減されます。
次の例は、汎用インターフェースを示しています:
class Animal():
def walk(self):
pass
def swim(self):
pass
class Cat(Animal):
def walk(self):
print("Struts")
def fly(self):
raise Exception("Cats don't swim")
class Duck(Animal):
def walk(self):
print("Waddles")
def swim(self):
print("Floats")
子クラスは親 Animal
から継承します walk
を含むクラス そしてfly
メソッド。特定の動物では両方の機能が許容されますが、一部の動物には重複した機能があります。
この状況に対処するには、インターフェイスを小さなセクションに分割します。例:
class Walk():
def walk(self):
pass
class Swim(Walk):
def swim(self):
pass
class Cat(Walk):
def walk(self):
print("Struts")
class Duck(Swim):
def walk(self):
print("Waddles")
def swim(self):
print("Floats")
Fly
クラスは Walk
から継承します 、適切な子クラスに追加機能を提供します。この例は、インターフェイス分離の原則を満たしています。
魚などの別の動物を追加するには、魚が歩くことができないため、インターフェイスをさらに細分化する必要があります。
依存性逆転の原則 (DIP)
依存関係逆転の原則は、次のように述べています。「具象ではなく抽象に依存する」
この原則は、抽象化レイヤーを追加することにより、クラス間の接続を減らすことを目的としています。依存関係を抽象化に移動すると、コードが堅牢になります。
次の例は、抽象レイヤーを使用しないクラスの依存関係を示しています:
class LatinConverter:
def latin(self, name):
print(f'{name} = "Felis catus"')
return "Felis catus"
class Converter:
def start(self):
converter = LatinConverter()
converter.latin('Cat')
if __name__ == '__main__':
converter = Converter()
converter.start()
この例には 2 つのクラスがあります:
LatinConverter
架空の API を使用して、動物のラテン名を取得します (ハードコードされた「Felis catus
」 簡単にするために」)Converter
LatinConverter
のインスタンスを使用する高レベル モジュールです。 および提供された名前を変換するその関数。Converter
LatinConverter
に大きく依存 API に依存するクラス。このアプローチは原則に違反しています。
依存関係逆転の原則では、2 つのクラス間に抽象化インターフェース レイヤーを追加する必要があります。
ソリューションの例は次のようになります:
from abc import ABC
class NameConverter(ABC):
def convert(self,name):
pass
class LatinConverter(NameConverter):
def convert(self, name):
print('Converting using Latin API')
print(f'{name} = "Felis catus"')
return "Felis catus"
class Converter:
def __init__(self, converter: NameConverter):
self.converter = converter
def start(self):
self.converter.convert('Cat')
if __name__ == '__main__':
latin = LatinConverter()
converter = Converter(latin)
converter.start()
Converter
クラスは NameConverter
に依存するようになりました LatinConverter
の代わりにインターフェイス 直接。将来の更新では、NameConverter
を通じて別の言語と API を使用して名前変換を定義できます。
堅実な原則が必要な理由
SOLID の原則は、設計パターンの問題に対処するのに役立ちます。 SOLID 原則の全体的な目標は、コードの依存関係を減らすことであり、新しい機能を追加したり、コードの一部を変更したりしても、ビルド全体が壊れることはありません。
SOLID の原則をオブジェクト指向設計に適用した結果、コードの理解、管理、保守、および変更が容易になります。ルールは大規模なプロジェクトにより適しているため、SOLID の原則を適用すると、開発ライフサイクル全体の速度と効率が向上します。
堅実な原則は今でも有効ですか?
SOLID の原則は 20 年以上前のものですが、依然としてソフトウェア アーキテクチャ設計の優れた基盤を提供しています。 SOLID は、オブジェクト指向プログラミングだけでなく、最新のプログラムや環境に適用できる健全な設計原則を提供します。
SOLID の原則は、コードが人によって記述および変更され、モジュールに編成され、内部または外部の要素が含まれている状況に適用されます。
結論
SOLID の原則は、ソフトウェア アーキテクチャ設計のための優れたフレームワークとガイドを提供するのに役立ちます。このガイドの例は、Python などの動的に型付けされた言語でさえ、原則をコード設計に適用することでメリットがあることを示しています。
次に、チームが DevOps を最大限に活用するのに役立つ 9 つの DevOps 原則についてお読みください。
クラウドコンピューティング