クリーム色の背景、色付き表示、高コントラスト、大きな文字のほうが読みやすいですか?配色と文字を選ぶと、すべてのページに一度で反映されます。

無料レッスン

設計原則: 単一責任と開放閉鎖

このレッスンのあとで: 仕事を2つ持つクラスや、種類が増えるたびに直すコードを見つけ、分けて直すことができる。

60分のレッスンコマンド6件毎日使う Linux コマンドのラボ:エンジニアが暗記している30のコマンド

設計原則とは何か

設計原則とは、変更に強いクラスを作るための、経験から生まれた心得です。代表が、単一責任の原則と、開放閉鎖の原則です。

原則を知ると、直すたびに別の所が壊れるコードを、書く前に見分けられます。このレッスンでは、悪い例を見つけて、直します。

このレッスンのゴール

メモ

仕事を2つ持つクラスや、種類が増えるたびに直すコードを見つけ、分けて直すことができる。

キーワード

ことばいみれい
責任クラスが受け持つ仕事の1つのまとまり
単一責任の原則1つのクラスには、変わる理由を1つだけ持たせる心得
開放閉鎖の原則新しい種類は足して加え、今あるコードは直さずに済ませる心得
コードの臭い設計の問題がありそうだと知らせる、コードの形elif の連続
リファクタリング動きを変えずに、中の作りを整えること

学生が入力するセルは、このプレビューでは空白です。

1つのクラスは、変わる理由を1つだけ持つ

注文のクラスが、合計の計算と、画面の文面と、ファイルへの保存を全部書いているとします。

文面を変えたい人も、保存先を変えたい人も、同じクラスを開きます。変わる理由が3つあるのです。これを、単一責任の原則に反すると言います。

仕事が3つあるクラス

コード: python
class Order:
    def __init__(self, prices):
        self.prices = prices

    # 理由1: 合計の計算の決まりが変わる。
    def total(self):
        return sum(self.prices)

    # 理由2: 画面や紙の見た目が変わる。
    def to_text(self):
        return "合計 " + str(self.total()) + "円"

    # 理由3: 保存の場所や形式が変わる。
    def save(self, path):
        with open(path, "w") as f:
            f.write(self.to_text())

例: 注文のクラスが変わる理由

メソッド仕事変わる理由直す人
total合計を計算する値引きの決まりが変わる業務の担当
to_text文面を作る表示の形が変わるデザインの担当
saveファイルに保存する保存先が変わる運用の担当

学生が入力するセルは、このプレビューでは空白です。

仕事ごとにクラスを分ける

直し方は、変わる理由ごとにクラスを分けることです。Order は計算だけ、OrderText は文面だけ、OrderFile は保存だけを受け持ちます。

分けると、文面を直しても、計算のクラスは開かずに済みます。テストも、仕事ごとに書けます。

3つの仕事を3つのクラスに分ける

コード: python
class Order:
    def __init__(self, prices):
        self.prices = prices

    def total(self):
        return sum(self.prices)


class OrderText:
    def render(self, order):
        return "合計 " + str(order.total()) + "円"


class OrderFile:
    def save(self, text, path):
        with open(path, "w") as f:
            f.write(text)

開放閉鎖の原則は、足して増やす

運賃を種類で分ける if と elif の関数は、新しい種類のたびに、関数の中を直します。直すたびに、今動いている種類を壊す危険があります。

種類ごとにクラスを作れば、新しい種類はクラスを足すだけです。今あるコードは、直さずに済みます。これが開放閉鎖の原則です。

直す形と足す形の比べ方

コード: python
# 閉じた形: 種類が増えるたびに、この関数を直します。
def fare(kind, base):
    if kind == "child":
        return base // 2
    elif kind == "senior":
        return base * 80 // 100
    return base


# 開いた形: 新しい種類は、クラスを足すだけです。
class Student:
    def fare(self, base):
        return base * 70 // 100


print(Student().fare(600))

ラボで設計の臭いを数える

ラボの端末で、2つのファイルを作り、設計の臭いを数えます。6つの Run を、上から順に押してください。

1つ目のファイルは、仕事が3つのクラスです。2つ目は、elif が続く関数です。

作業用のフォルダを作る

コマンド
mkdir -p ~/oop/principles

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

そのフォルダへ移る

コマンド
cd ~/oop/principles

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

仕事が3つのクラスを書く

コマンド
printf 'class Order:\n    def __init__(self, prices):\n        self.prices = prices\n\n    def total(self):\n        return sum(self.prices)\n\n    def to_text(self):\n        return "total " + str(self.total()) + " yen"\n\n    def save(self, path):\n        with open(path, "w") as f:\n            f.write(self.to_text() + "\\n")\n' > order.py

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

クラスのメソッドを並べる

コマンド
grep -n "def " order.py

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

メソッドの一覧が、仕事の一覧になる

出力は、__init__、total、to_text、save の4行で、行の番号が付きます。

total は計算、to_text は文面、save は保存です。1つのクラスに、3つの仕事があると、一覧から分かります。

種類で分かれる関数を書く

コマンド
printf 'def fare(kind, base):\n    if kind == "adult":\n        return base\n    elif kind == "child":\n        return base // 2\n    elif kind == "senior":\n        return base * 80 // 100\n    return base\n\n\nprint(fare("child", 600))\n' > smell.py

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

elif の行を数える

コマンド
grep -c elif smell.py

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

elif の数は、種類の数に合わせて増える

出力は 2 です。3つの種類を分けるのに、elif が2行あります。

種類が1つ増えるたびに、この数も1つ増えます。数が増え続けるなら、クラスに分けるべき合図です。

よくある間違い

注意

「単一責任とは、メソッドを1つしか持たないことだ」と考える人が多くいます。それは間違いです。1つのクラスに、メソッドがいくつあっても構いません。変わる理由が1つなら、守れています。

このラボとソースについて: 毎日使う Linux コマンドのラボ:エンジニアが暗記している30のコマンド

授業で使いますか?このモジュールを学生に割り当てると、教員用ガイド、自動採点つきの課題、学生ごとのラボを使えます。