Prinsip substitusi Liskov: Jika subtipe menerapkan beberapa perilaku ekstra, yang tidak ada dalam tipe, maka apakah ini pelanggaran terhadap LSP?

8

Dalam upaya saya untuk menulis kode yang lebih baik dan lebih bersih, saya belajar tentang prinsip-prinsip SOLID. Dalam hal ini, LSP terbukti sedikit sulit untuk dipahami dengan baik.

Keraguan saya adalah bagaimana jika saya memiliki beberapa metode tambahan dalam subtipe saya, S, yang tidak ada dalam tipe, T, apakah ini akan selalu menjadi pelanggaran terhadap LSP? Jika ya, lalu bagaimana extendsaya kelas saya?

Misalnya, katakanlah kita memiliki Birdtipe. Dan subtipe-nya adalah Eagledan Humming Bird. Sekarang kedua subtipe memiliki beberapa perilaku umum sebagai Bird. Tetapi Eaglejuga memiliki perilaku predator yang baik (yang tidak ada pada Birdtipe umum ), yang ingin saya gunakan . Karenanya, sekarang saya tidak akan bisa melakukan ini:

Bird bird = new Eagle();

Jadi apakah memberi Eagleperilaku ekstra itu melanggar LSP?

Jika ya, maka itu berarti saya tidak dapat memperpanjang kelas saya karena itu akan menyebabkan pelanggaran LSP?

class Eagle extends Bird {
   //we are extending Bird since Eagle has some extra behavior also
}

Perluasan kelas harus diizinkan sesuai dengan prinsip Terbuka / Tertutup bukan?

Terima kasih sebelumnya telah menjawab! Seperti yang Anda lihat dengan jelas, LSP membuat saya bingung seperti apa pun.

Sunting: Lihat jawaban SO ini. Dalam hal ini lagi, ketika Carmemiliki perilaku tambahan seperti ChangeGear, itu melanggar LSP. Jadi, lalu bagaimana kita memperluas kelas, tanpa melanggar LSP?

pengguna270386
sumber
Saya telah melalui itu, tetapi tidak menjawab pertanyaan saya. Saya telah membaca banyak jawaban sebenarnya, tetapi sejauh ini tidak ada bantuan.
user270386
1
itu ada di sana di jawaban atas, sudahkah Anda membacanya: Setiap kali Anda mendapatkan satu kelas dari yang lain, pikirkan tentang kelas dasar dan apa yang orang mungkin anggap tentang hal itu ... Kemudian pikirkan "apakah asumsi-asumsi itu tetap berlaku di subkelas saya?" Jika tidak, pikirkan kembali desain Anda.
nyamuk
@gnat, Ya saya lakukan, tapi saya agak lambat :) Dan saya biasanya membutuhkan lebih banyak penjelasan daripada yang mungkin diperlukan orang lain. Setelah membaca jawaban menyeluruh David Arno, saya bisa menghubungkannya dengan kalimat itu sekarang.
user270386
1
@ Frankhileman banyak dari kita bekerja dengan kompiler dan basis kode yang kurang ideal. Bahkan jika kita tidak melakukannya, itu masih merupakan hal yang baik ketika manusia juga memahami bagaimana cara menghormatinya.
candied_orange

Jawaban:

10

Keraguan saya adalah bagaimana jika saya memiliki beberapa metode tambahan dalam subtipe saya, S, yang tidak ada dalam tipe, T, apakah ini akan selalu menjadi pelanggaran terhadap LSP?

Jawaban yang sangat sederhana: tidak.

Poin ke LSP adalah yang Sharus diganti T. Jadi jika Tmengimplementasikan suatu deletefungsi, Sharus mengimplementasikannya juga dan harus melakukan delete ketika dipanggil. Namun, Sbebas untuk menambahkan fungsionalitas tambahan di atas yang Tdisediakan. Konsumen dari T, ketika diberi Sakan tidak menyadari fungsionalitas tambahan ini, tetapi diizinkan untuk digunakan oleh konsumen Ssecara langsung.

Contoh yang sangat dibuat-buat tentang bagaimana prinsip tersebut dapat dilanggar adalah:

class T
{
    bool delete(Item x)
    {
        if (item exists)
        {
            delete it
            return true;
        }
        return false;
    }
}

class S extends T
{
    bool delete(Item x)
    {
        if (item doesn't exist)
        {
            add it
            return false;
        }
        return true;
    }
}

Jawaban yang sedikit lebih rumit: tidak, asalkan Anda tidak mulai mempengaruhi status atau perilaku yang diharapkan lainnya dari tipe dasar.

Misalnya, berikut ini akan menjadi pelanggaran:

class Point2D
{
    private readonly double _x;
    private readonly double _y;

    public virtual double X => _x;
    public virtual double Y => _y;

    public Point2D(double x, double y) => (_x, _y) = (x, y);
}

class MyPoint2D : Point2D
{
    private double _x;
    private double _y;

    public override double X => _x;
    public override double Y => _y;

    public MyPoint2D(double x, double y) : 
        base(x, y) => (_x, _y) = (x, y);

    public void Update(double x, double y) => (_x, _y) = (x, y);
}

Jenis, Point2D, adalah kekal; keadaannya tidak bisa diubah. Dengan MyPoint2D, saya sengaja menghindari perilaku itu untuk membuatnya bisa berubah. Itu melanggar batasan sejarah Point2Ddan merupakan pelanggaran terhadap LSP.

David Arno
sumber
Mungkin tambahan yang bagus untuk jawaban ini akan menjadi contoh perilaku yang dapat ditambahkan ke deletefungsi yang akan menjadi pelanggaran LSP?
Andy Hunt
1
@ user270386: Tidak. LSP bukan tentang struktur subclass, ini tentang perilaku kode subclass, dibandingkan dengan perilaku kelas dasar. Ini bukan fungsi tambahan dengan sendirinya yang melanggar batasan sejarah; sebaliknya, kendala dilanggar jika fungsi baru ini melakukan sesuatu yang tidak terduga (misalnya dilarang) di kelas dasar (dan ini termasuk hal-hal yang dapat diekspresikan melalui bahasa, serta hal-hal yang hanya dapat diekspresikan melalui dokumentasi).
Filip Milovanovic
1
Mungkin perlu dicatat bahwa jika T adalah antarmuka yang ketat, kelas sepenuhnya abstrak, atau bahkan pola objek nol maka S cukup banyak tentang struktur. T adalah kode. Itu belum tentu spesifikasi, dokumen persyaratan, atau pemilik produk. Kami hanya peduli tentang pelanggaran LSP ketika T digunakan untuk memanifestasikan kendala yang harus selalu berlaku. Tidak setiap kelas melakukannya. Tetapi jika Anda memberitahu S untuk menghapus dan dengan desain S tidak melakukan apa-apa yang lebih baik dengan sisa kode. "Aku tidak bisa memberitahumu kalau itu benar. Itu hanya bisa memberi tahu Anda bahwa Anda sebaiknya memeriksa sebelum melakukan ini.
candied_orange
1
(lanjutan) IMO, itu persyaratan untuk substitusi yang mendorong semua ini. Substitutabilitas benar-benar berasal dari kemampuan untuk memperlakukan dua implementasi (perilaku) yang berbeda sebagai setara pada beberapa tingkat abstraksi yang lebih tinggi yang ditentukan oleh kontrak yang ditentukan oleh supertype. Dalam pengertian itu, struktur adalah sarana untuk mencapai tujuan.
Filip Milovanovic
1
@ Davidvido "kode tipe dasar" - bukan spesifikasi, atau dapat diakses tentu.
Frank Hileman
3

Tentu saja tidak. Jika objek Eagle dapat digunakan oleh kode apa pun yang mengharapkan Bird atau subclass, dan berperilaku sebagai Bird harus berperilaku, Anda baik-baik saja.

Tentu saja perilaku Elang hanya dapat digunakan oleh kode yang sadar bahwa itu adalah objek seperti itu. Kami berharap bahwa beberapa kode akan secara eksplisit membuat objek Eagle dan menggunakannya sebagai objek Eagle, sambil dapat menggunakan kode apa pun yang mengharapkan objek Bird.

gnasher729
sumber