2013-05-29 7 views
6

SquareをRectangleクラスの継承クラスにすることは、LSP(Liskov置換原理)に違反していると言って、悪い習慣です。私はまだそれを得ることはありません、私はRubyでサンプルコードを作った:正方形と長方形の継承で何が問題になっていますか?

class Rectangle 
    attr_accessor :width, :height 
    def initialize(width, height) 
     @width = width 
     @height = height 
    end 
end 

class Square < Rectangle 
    def initialize(length) 
     super(length, length) 
    end 
    def width=(number) 
     super(number) 
     @height = number 
    end 

    def height=(number) 
     super(number) 
     @width = number 
    end 
end 


s = Square.new(100) 

s.width = 50 

puts s.height 

誰もがそれと間違って何を教えてもらえますか?

+0

ランパースペースプリンセス? http://www.youtube.com/watch?v=pJTrD3R5cj0 – paxdiablo

+0

うわー、それは興味深いですが、私はそれを得ることはできません – mko

+1

yozloy、謝罪、私はちょうどあなたがそれを意識していない人が検索する必要がないようにLSPを設定します。 – paxdiablo

答えて

4

"エッセンス"ではなく、動作に基づいて継承でできることを制限しているようだから、私は常にLiskovに熱心ではありません。私の見解では、継承は常に「まったく」の関係であり、「まったく同じような行動」ではありません。

the wikipedia articleが、これはあなたの正確な例を使用して、いくつかのことで悪いと考えている理由のように、詳細に入る、と述べた:

LSPに違反する典型的な例は、長方形から派生広場クラスですgetterメソッドとsetterメソッドが幅と高さの両方に存在すると仮定します。

Squareクラスは、常に幅が高さと等しいとみなします。 Rectangleが予想されるコンテキストでSquareオブジェクトが使用されている場合、Squareのディメンションが独立して変更できない(またはそうではない)ため、予期しない動作が発生する可能性があります。

この問題は簡単には修正できません.Squareクラスのセッターメソッドを変更してSquare不変式を保持する(つまり、次元を同じに保つ)と、これらのメソッドはそのインスタンスの事後条件を弱める(違反する)長方形のセッター。寸法が独立して変更できること。

ので、同等のRectangleコードと一緒にあなたのコードを見て:出力は右に左に50と100になり

s = Square.new(100)   r = Rectangle.new(100,100) 
s.width = 50     r.width = 50 
puts s.height     puts r.height 

しかし、このが私の見解では、記事からの重要なビットである。このように、LSPの

違反、は、またはは、実際の問題であることに依存しない場合がありますLSPに違反するクラスを使用するコードによって実際に予想される事後条件または不変条件。言い換えれば

、行動を理解し、何も問題はありませんクラスを使用してコードを提供します。

ボトムライン、正方形は何リスコフの置換原則(LSP)の観点から、それで間違っていることは、あなたのRectangleのことです

+0

あなたの詳細な説明に感謝します。あなたの 'Rectangle'コードから得られないものの1つは、' r = Rectangle.new(100) 'という行です。' r = Rectangle.new(100、100) 'を意味しますか? – mko

+0

@yozloy、はい、申し訳ありませんが、私はそれが正方形を作った単一の引数のRectangleコンストラクタであると主張できましたが、cut'n'pasteエラーでした:-)今修正されました。 – paxdiablo

+0

@paxdiableそれを得ました! 's.height'の出力と' r.height'の出力を話して、私は '50'、' 100'が正しい出力だと思っています。 – mko

2

:-)長方形のルーズ十分な定義については、長方形の適切なサブセットでありますSquareは変更可能です。つまり、サブクラス内のセッターを明示的に再実装しなければならず、継承のメリットが失われます。 Rectangleを変更できない場合、つまり、既存の測定値を変更するのではなく新しい値を作成する場合は、LSPに違反する問題はありません。attr_readerを使用して

class Rectangle 
    attr_reader :width, :height 

    def initialize(width, height) 
    @width = width 
    @height = height 
    end 

    def area 
    @width * @height 
    end 
end 

class Square < Rectangle 
    def initialize(length) 
    super(length, length) 
    end 
end 

はゲッターではなく、セッター、したがって不変性を提供します。この実装では、RectanglesSquaresの両方が、heightとの可視性を提供します。正方形の場合、これらは常に同じで、領域の概念は一貫しています。

+0

ああ!私はどこから来ているのかを見ることができ、それは良い説明です。しかし、これはオブジェクトの再利用をかなり難しくしているようです。オブジェクトのサイズを変更するには、変更された属性を持つ新しいものを作成し、古いものを破棄する必要があります。それはあなたの答えをあまり有効にしません。私の目にはLSPの有用性が減ります。 – paxdiablo

+0

@pjsなぜmutableは継承の利益を失うのですか?私はsetterメソッドを再実装したと思う、 'Square'クラスは' Rectangle'クラスで定義されたメソッドを再利用できます、それは一種の利益ですか? – mko

+0

@paxdiablo:LSPを主張しているわけではありませんが、私はそれを説明しようとしています。しかし、私は不変オブジェクトを推薦する傾向があります。次元の異なる長方形は、異なる長方形です!これは多くの場合、物事をより安全にします。たとえば、その領域で順序付けられたバイナリ検索ツリーに一連の長方形を配置することを検討してください。今度は、それらの1つの次元を変更すると、ツリーは将来のアクセスのために不思議に失敗し始めます。その要素の1つが突然ツリーの基本的な注文プロパティに違反します。そのようなバグは本当に追跡が難しいかもしれません。 – pjs

0

抽象基本クラスまたはインタフェース(何かがインタフェースか抽象クラスかは、LSPとは無関係の実装の詳細です)を考えてみてください。ReadableRectangle;読み取り専用のプロパティWidthHeightがあります。同じプロパティを持ちますが、WidthHeightが常に等しいことを契約上保証するタイプReadableSquareを導出することは可能です。

ReadableRectangleから、一つは(コンストラクタで高さと幅をとり、HeightWidth特性は常に同じ値を返すことを保証)、およびMutableRectangleコンクリート型ImmutableRectangleを定義することができます。コンクリートタイプMutableRectangleを定義することもできます。これにより、高さと幅をいつでも設定できます。

物事の「正方形」側では、ImmutableRectangleReadableSquareの両方をImmutableSquareに置き換える必要があります。しかし、ReadableSquareを代用することができるだけでなく、を代用することができる。また、ImmutableRectangleの代わりにImmutableRectangleタイプを継承した場合の値が制限される。 ImmutableRectangleが抽象型またはインタフェースの場合、ImmutableSquareクラスは次元を保持するために2つではなく1つのフィールドを使用する必要があります(2つのフィールドを持つクラスの場合、節約は大したことではありませんが、より多くのフィールド、貯蓄が重要な可能性があります)。しかし、ImmutableRectangleが具象型の場合、派生型はその基底のすべてのフィールドを持つ必要があります。

一部のタイプの四角形は、対応するタイプの矩形で置き換えられますが、変更可能な四角形は、変更可能な矩形では置き換えられません。

関連する問題