目次:
リスコフ置換の原則(LSP) ってドユコト?
説明しよう。LSP(Liskov Substitution Principle)とは。
「スーパークラスのオブジェクトは、プログラムの正しさを変えることなくサブクラスのオブジェクトに置き換えることができるべきである」というもの。
ソフトウェアの複雑性によく効くの?
YES。
- オブジェクト指向プログラムの設計を簡素化することができるよ。
- LSPは、各クラスをゼロから設計しないよ。
- より単純なクラスからより複雑なクラスを構築するようにするよ。
- 継承をうまいこと使用することを促しているよ。
テストのし易さによく効くの?
YES。
- テストを書く際に考慮しなければならないケースを減らすことができるよ。
- 単純にコードのテストが容易になるよ。
- 例えば、スーパークラスのオブジェクトとサブクラスのオブジェクトが交換可能な場合、スーパークラスのテストをサブクラスのテストにも使用できよね。
- ということは、書かなければならないテストコードの量が減るよ。
ソフトウェアを変更しやすくなるの?
YES。
- プログラムの変更がしやすくなるよ。
- とういうことは、プログラムに新しい機能を追加したり、既存の機能を修正したりすることが簡単になるよ。
- なぜなら、基礎となるコードに変更を加えなくても、より高い抽象度で変更を加えることができる場合が多いからだよ。
コードはドウダ?
コードの説明
要するに、スーパークラスとサブクラスの親子関係があればLSPは実現できるって話。
こうするべきだ!
スーパークラスのオブジェクトは、プログラムの正しさに影響を与えることなく、サブクラスのオブジェクトに置き換えることができるように作らんかいっ。
なぜならば!
スーパークラスのオブジェクトは、サブクラスのオブジェクトが想定される場所(使われる場所)であればどこでも使用可能で、プログラムの動作は変わらないはずだからだ!。
リスコフ置換の原則(LSP)をJavaで書くと?
Javaだとこう。
LSPは継承とポリモーフィズムを利用して実装することができる。サブクラスは,スーパークラスによって確立された契約(というか制約か?)を維持する方法で,そのスーパークラスのメソッドをオーバーライドする、つまり置換する必要がある。
ということで、サブクラスのメソッドのシグネチャはスーパークラスのメソッドのシグネチャと同じであるべきで、サブクラスのメソッドの実装はスーパークラスの実装と互換性があるべきということなのでアール。
class Shape { int width, height; public int getArea() { return width * height; } } class Rectangle extends Shape { @Override public int getArea() { return width * height; } } class Square extends Shape { @Override public int getArea() { return width * width; } } public class Main { public static void main(String[] args) { Shape s = new Rectangle(); s.width = 10; s.height = 20; System.out.println("Area of rectangle: " + s.getArea()); s = new Square(); s.width = 10; s.height = 20; System.out.println("Area of square: " + s.getArea()); } }
リスコフ置換の原則(LSP)をGOで書くと?
Goだとこう。
Goの場合はインターフェースしかないので。インタフェースの使用によって実装することになる。インターフェースを実装する型は、そのインターフェースで定義された全てのメソッドの実装を提供しなければならない。
ということで、サブクラスのオブジェクトは、必要なメソッドをすべて実装している限り、インターフェイスのオブジェクトが期待される場所で使用することができるのでアール。
type Shape interface { Area() int } type Rectangle struct { width, height int } func (r Rectangle) Area() int { return r.width * r.height } type Square struct { side int } func (s Square) Area() int { return s.side * s.side } func main() { var s Shape s = Rectangle{width: 10, height: 20} fmt.Println("Area of rectangle:", s.Area()) s = Square{side: 10} fmt.Println("Area of square:", s.Area()) }
リスコフ置換の原則(LSP)をClojureで書くと?
Clojureだとこう。
プロトコルの使用によってLSPを実装することができる。
プロトコルは、ある型が実装できるメソッドの集まりで、プロトコルに含まれるメソッドの実装を定義することで、型はプロトコルを実装することができます。
ということで、サブクラスが必要なメソッドを実装している限り、プロトコルのオブジェクトが期待される場所で、サブクラスのオブジェクトを使用することができるのでアール。
(defprotocol Shape (area [this])) (defrecord Rectangle [width height] Shape (area [this] (* (:width this) (:height this)))) (defrecord Square [side] Shape (area [this] (* (:side this) (:side this)))) (def s (->Rectangle 10 20)) (println "Area of rectangle:" (area s)) (def s (->Square 10))
リスコフ置換の原則(LSP)をPython で書くと?
Pythonだとこう。
Pythonでは、LSPは継承とポリモーフィズムの使用によって実装することができる。サブクラスは、スーパークラスによって確立された契約を維持する方法で、そのスーパークラスのメソッドをオーバーライドする必要がある。
ということで、サブクラスのメソッドのシグネチャはスーパークラスのメソッドのシグネチャと同じであるべきで、サブクラスのメソッドの実装はスーパークラスの実装と互換性があるべき。
Pythonはダックタイピングもサポートしている。クラスや継承の階層に関係なく、同じメソッドを持つオブジェクトが期待されるところならどこでもそのオブジェクトを使用することができるのでアール。
class Shape: def __init__(self, width, height): self.width = width self.height = height def get_area(self): return self.width * self.height class Rectangle(Shape): pass class Square(Shape): def get_area(self): return self.width * self.width if __name__ == "__main__": s = Rectangle(10, 20) print("Area of rectangle:", s.get_area()) s = Square(10, 20) print("Area of square:", s.get_area())
コメント