コンポジションと継承
CoddyのPythonジャーニー「Object Oriented Programming」セクションの一部 — レッスン 37/64。
オブジェクト指向プログラミングは、コードの再利用のために、継承(「is-a」関係)とコンポジション(「has-a」関係)という2つの主要なアプローチを提供します。
継承によって「is-a」関係が作成される例を次に示します:
class Animal:
def __init__(self, name):
self.name = name
def eat(self):
return f"{self.name} is eating"
class Dog(Animal): # DogはAnimalの「is-a」関係
def bark(self):
return "Woof!"
dog = Dog("Buddy")
print(dog.eat()) # 継承されたメソッド
print(dog.bark()) # 独自のメソッド以下は、「has-a」関係を作成するコンポジションの例です:
class Engine:
def start(self):
return "Engine started"
def stop(self):
return "Engine stopped"
class Car: # CarはEngineを「持っている(has an)」
def __init__(self):
self.engine = Engine() # コンポジション
def start(self):
return self.engine.start()
car = Car()
print(car.start()) # コンポジションされたエンジンを使用より複雑な例で、両方のアプローチを比較してみましょう:
# 継承によるアプローチ
class Bird:
def move(self):
return "Flying"
class Duck(Bird):
def quack(self):
return "Quack!"
# コンポジションによるアプローチ
class FlyBehavior:
def move(self):
return "Flying"
class SwimBehavior:
def move(self):
return "Swimming"
class VersatileDuck:
def __init__(self):
self.fly_behavior = FlyBehavior()
self.swim_behavior = SwimBehavior()
def fly(self):
return self.fly_behavior.move()
def swim(self):
return self.swim_behavior.move()
def quack(self):
return "Quack!"両方のアプローチをテストします:
# 継承
duck1 = Duck()
print(duck1.move()) # 飛行中
print(duck1.quack()) # ガーガー!
# コンポジション
duck2 = VersatileDuck()
print(duck2.fly()) # 飛行中
print(duck2.swim()) # 泳ぎ中
print(duck2.quack()) # ガーガー!出力:
Buddy is eating
Woof!
Engine started
Flying
Quack!
Flying
Swimming
Quack!主な違い:
継承:
- 親と子の間の密結合
- 「Is-a」関係
- 親への変更はすべての子に影響します
- 真の階層関係に最適です
コンポジション:
- オブジェクト間の疎結合
- "Has-a" 関係
- より柔軟 - 実行時に動作を変更可能
- テストや修正が容易
重要なポイント:真の「is-a」関係がある場合は継承を使用します。柔軟性と疎結合が必要な場合はコンポジションを使用します。「継承よりコンポジション(composition over inheritance)」という原則は、その柔軟性と保守性の高さから、ほとんどの場合においてコンポジションを優先することを推奨しています。
チャレンジ
中級このチャレンジでは、継承とコンポジションの両方のパターンを使用してメディアライブラリシステムを実装します。
各ファイル内の TODO コメントに従って、以下のファイルを編集する必要があります。
media.py、book.py、movie.py、musicalbum.py- 継承の実装用mediaitem.py、bookcomposition.py、moviecomposition.py、musicalbumcomposition.py- コンポジションの実装用
各メディアタイプには以下が必要です。
- タイトル (Title)、制作者 (creator) (著者/監督/アーティスト)、および年 (year) の属性
- 適切な
display_info()メソッド
チートシート
オブジェクト指向プログラミングでは、コードの再利用のために主に2つのアプローチが提供されています。継承(「is-a」関係)とコンポジション(「has-a」関係)です。
継承の例:
class Animal:
def __init__(self, name):
self.name = name
def eat(self):
return f"{self.name} is eating"
class Dog(Animal): # DogはAnimalである ("is an" Animal)
def bark(self):
return "Woof!"
dog = Dog("Buddy")
print(dog.eat()) # 継承されたメソッド
print(dog.bark()) # 自身のメソッドコンポジションの例:
class Engine:
def start(self):
return "Engine started"
class Car: # CarはEngineを持っている ("has an" Engine)
def __init__(self):
self.engine = Engine() # コンポジション
def start(self):
return self.engine.start()
car = Car()
print(car.start()) # 構成されたエンジンを使用主な違い:
継承:
- 親と子の間の密結合
- 「Is-a」関係
- 親への変更がすべての子に影響する
- 真の階層関係に最適
コンポジション:
- オブジェクト間の疎結合
- 「Has-a」関係
- より柔軟 - 実行時に動作を変更可能
- テストと修正が容易
真の「is-a」関係には継承を使用します。柔軟性と疎結合のためにはコンポジションを使用します。「継承よりコンポジション(composition over inheritance)」という原則は、ほとんどの場合においてコンポジションを優先することを推奨しています。
自分で試してみよう
from media import Media
from book import Book
from movie import Movie
from musicalbum import MusicAlbum
from mediaitem import MediaItem
from bookcomposition import BookComposition
from moviecomposition import MovieComposition
from musicalbumcomposition import MusicAlbumComposition
# 総合的なテストケースハンドラー
test_case = input()
def test_inheritance_basic():
book = Book("The Hobbit", "J.R.R. Tolkien", 1937)
movie = Movie("The Matrix", "Wachowski Sisters", 1999)
album = MusicAlbum("Abbey Road", "The Beatles", 1969)
assert book.display_info() == "Book: The Hobbit by J.R.R. Tolkien (1937)"
assert movie.display_info() == "Movie: The Matrix directed by Wachowski Sisters (1999)"
assert album.display_info() == "Music Album: Abbey Road by The Beatles (1969)"
def test_composition_basic():
book_comp = BookComposition("Dune", "Frank Herbert", 1965)
movie_comp = MovieComposition("Inception", "Christopher Nolan", 2010)
album_comp = MusicAlbumComposition("Thriller", "Michael Jackson", 1982)
assert book_comp.display_info() == "Book: Dune by Frank Herbert (1965)"
assert movie_comp.display_info() == "Movie: Inception directed by Christopher Nolan (2010)"
assert album_comp.display_info() == "Music Album: Thriller by Michael Jackson (1982)"
def test_inheritance_relationships():
book = Book("Test Book", "Test Author", 2000)
movie = Movie("Test Movie", "Test Director", 2001)
album = MusicAlbum("Test Album", "Test Artist", 2002)
assert isinstance(book, Book)
assert isinstance(book, Media)
assert isinstance(movie, Movie)
assert isinstance(movie, Media)
assert isinstance(album, MusicAlbum)
assert isinstance(album, Media)
def test_attribute_access():
# 継承アプローチのテスト
book = Book("Test Book", "Test Author", 2000)
assert book.title == "Test Book"
assert book.creator == "Test Author"
assert book.year == 2000
# コンポジションアプローチのテスト
book_comp = BookComposition("Test Book", "Test Author", 2000)
assert book_comp.media.title == "Test Book"
assert book_comp.media.creator == "Test Author"
assert book_comp.media.year == 2000
def test_polymorphism():
media_list = [
Book("Book1", "Author1", 2001),
Movie("Movie1", "Director1", 2002),
MusicAlbum("Album1", "Artist1", 2003)
]
assert media_list[0].display_info() == "Book: Book1 by Author1 (2001)"
assert media_list[1].display_info() == "Movie: Movie1 directed by Director1 (2002)"
assert media_list[2].display_info() == "Music Album: Album1 by Artist1 (2003)"
def test_edge_cases():
# 空の文字列
book = Book("", "", 0)
assert book.display_info() == "Book: by (0)"
# 特殊文字 - ここでの構文エラーを修正
movie = Movie("Test\"Movie", "Test\\nDirector", -1)
assert movie.display_info() == "Movie: Test\"Movie directed by Test\\nDirector (-1)"
# 極端な年
album = MusicAlbum("Test Album", "Test Artist", 9999)
assert album.display_info() == "Music Album: Test Album by Test Artist (9999)"
def test_stress():
# 多数のメディアオブジェクトを作成
books = [Book(f"Book{i}", f"Author{i}", 2000+i) for i in range(100)]
movies = [Movie(f"Movie{i}", f"Director{i}", 2000+i) for i in range(100)]
albums = [MusicAlbum(f"Album{i}", f"Artist{i}", 2000+i) for i in range(100)]
for i, book in enumerate(books):
assert book.display_info() == f"Book: Book{i} by Author{i} ({2000+i})"
for i, movie in enumerate(movies):
assert movie.display_info() == f"Movie: Movie{i} directed by Director{i} ({2000+i})"
for i, album in enumerate(albums):
assert album.display_info() == f"Music Album: Album{i} by Artist{i} ({2000+i})"
# 入力に基づいて適切なテストを実行
if test_case == "default_test" or test_case == "":
test_inheritance_basic()
test_composition_basic()
print("All tests passed!")
elif test_case == "inheritance_test":
test_inheritance_basic()
test_inheritance_relationships()
print("Inheritance tests passed!")
elif test_case == "composition_test":
test_composition_basic()
print("Composition tests passed!")
elif test_case == "polymorphism_test":
test_polymorphism()
print("Polymorphism tests passed!")
elif test_case == "attribute_test":
test_attribute_access()
print("Attribute tests passed!")
elif test_case == "edge_cases":
test_edge_cases()
print("Edge case tests passed!")
elif test_case == "stress_test":
test_stress()
print("Stress tests passed!")このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。