Apakah prinsip pemisahan antarmuka berlaku untuk metode konkret?

10

Seperti prinsip pemisahan antarmuka menyarankan tidak ada klien harus dipaksa untuk bergantung pada metode yang tidak digunakan, sehingga klien tidak boleh menerapkan metode kosong untuk metode antarmuka, jika tidak metode antarmuka ini harus dimasukkan ke antarmuka lain.

Tetapi bagaimana dengan metode konkret? Haruskah saya memisahkan metode yang tidak akan digunakan oleh setiap klien? Pertimbangkan kelas berikut:

public class Car{
    ....

    public boolean isQualityPass(){
        ...
    }

    public int getTax(){
        ...
    }

    public int getCost(){
        ...
    }
}

public class CarShop{
    ...
    public int getCarPrice(int carId){
        Car car=carList[carId];
        int price=car.getTax() + car.getCost()... (some formula);
        return price;
    }
}

pada kode di atas, CarShop tidak menggunakan metode isQualityPass () di Car sama sekali, haruskah saya memisahkan isQualityPass () ke dalam kelas baru:

public class CheckCarQualityPass{
    public boolean isQualityPass(Car car){
    }
}

untuk mengurangi kopling CarShop? Karena saya pikir sekali jika isQualityPass () membutuhkan dependensi tambahan, misalnya:

public boolean isQualityPass(){
    HttpClient client=...
}

CarShop akan bergantung pada HttpClient bahkan tidak pernah menggunakan HttpClient sebenarnya. Jadi pertanyaan saya adalah: sesuai dengan prinsip antarmuka-pemisahan, haruskah saya memisahkan metode konkret yang tidak akan digunakan oleh setiap klien, sehingga metode-metode tersebut bergantung pada klien hanya ketika klien benar-benar menggunakan, untuk mengurangi sambungan?

ggrr
sumber
2
Apakah biasanya mobil tahu ketika melewati "kualitas"? Atau mungkin itu aturan bisnis yang bisa diringkas sendiri?
Laiv
2
Seperti kata antarmuka di ISP ini menyiratkan tentang antarmuka . Jadi, jika Anda memiliki metode di Carkelas Anda yang tidak Anda inginkan (semua) diketahui oleh pengguna, maka buat (lebih dari satu) antarmuka yang Cardiimplementasikan oleh kelas yang hanya menyatakan metode yang berguna dalam konteks antarmuka.
Timothy Truckle
@Lav Saya yakin kita akan melihat kendaraan yang tahu lebih dari itu segera. ;)
sandwich pemodelan terpadu
1
Mobil akan tahu apa yang diinginkan pembuatnya. Volkswagen tahu apa yang saya bicarakan :-)
Laiv
1
Anda menyebutkan antarmuka, tetapi tidak ada antarmuka dalam contoh Anda. Apakah kita berbicara tentang mengubah Mobil menjadi antarmuka dan metode apa yang akan disertakan dalam antarmuka tersebut?
Neil

Jawaban:

6

Dalam contoh Anda, CarShoptidak bergantung pada isQualityPass, dan tidak dipaksa untuk membuat implementasi kosong untuk suatu metode. Bahkan tidak ada antarmuka yang terlibat. Jadi istilah "ISP" sama sekali tidak cocok di sini. Dan selama metode suka isQualityPassadalah metode yang cocok dengan Carobjek, tanpa dibebani dengan tanggung jawab atau ketergantungan tambahan, ini baik-baik saja. Tidak perlu mengubah metode publik suatu kelas ke tempat lain hanya karena ada satu klien yang tidak menggunakan metode tersebut.

Namun, membuat kelas domain seperti Carlangsung bergantung pada sesuatu seperti HttpClientmungkin juga bukan ide yang baik, terlepas dari mana klien menggunakan atau tidak menggunakan metode ini. Memindahkan logika ke kelas yang terpisah CheckCarQualityPasstidak disebut "ISP", ini disebut "pemisahan masalah" . Perhatian terhadap objek mobil yang dapat digunakan kembali mungkin tidak untuk membuat panggilan HTTP eksternal, setidaknya tidak secara langsung, ini membatasi usabilitas dan terlebih lagi testabilitas terlalu banyak.

Jika isQualityPasstidak dapat dengan mudah dipindahkan ke kelas lain, alternatifnya adalah membuat Httppanggilan melalui antarmuka abstrak IHttpClientyang disuntikkan Carpada waktu konstruksi, atau dengan menyuntikkan seluruh strategi pengecekan "QualityPass" (dengan permintaan Http dienkapsulasi) ke Carobjek . Tapi itu hanya IMHO solusi terbaik kedua, karena meningkatkan kompleksitas keseluruhan alih-alih menguranginya.

Doc Brown
sumber
bagaimana dengan pola Strategi untuk menyelesaikan metode isQualityPass?
Laiv
@ Longv: secara teknis, ini akan berhasil, tentu (lihat edit saya), tetapi akan menghasilkan objek yang lebih kompleks Car. Itu tidak akan menjadi pilihan pertama saya untuk solusi (setidaknya tidak dalam konteks contoh yang dibuat-buat ini). Namun, mungkin lebih masuk akal dalam kode "nyata", saya tidak tahu.
Doc Brown
6

Jadi pertanyaan saya adalah: sesuai dengan prinsip antarmuka-pemisahan, haruskah saya memisahkan metode konkret yang tidak akan digunakan oleh setiap klien, sehingga metode-metode tersebut bergantung pada klien hanya ketika klien benar-benar menggunakan, untuk mengurangi sambungan?

Prinsip pemisahan antarmuka bukan tentang melarang akses ke apa yang tidak Anda butuhkan. Ini tentang tidak menuntut akses ke apa yang tidak Anda butuhkan.

Antarmuka tidak dimiliki oleh kelas yang mengimplementasikannya. Itu dimiliki oleh benda-benda yang menggunakannya.

public class CarShop{
    ...
    public int getCarPrice(int carId){
        Car car=carList[carId];
        int price=car.getTax() + car.getCost()... (some formula);
        return price;
    }
}

Apa yang digunakan di sini adalah getTax()dan getCost(). Yang ditekankan adalah segala sesuatu dapat diakses Car. Masalahnya adalah bersikeras Carberarti bersikeras akses isQualityPass()yang tidak diperlukan.

Ini bisa diperbaiki. Anda bertanya apakah itu dapat diperbaiki secara konkret. Bisa.

public class CarShop{
    ...
    public int getCarPrice(int carId){
        CarLiability carLiability=carLiabilityList[carId];
        int price=carLiability.getTax() + carLiability.getCost()... (some formula);
        return price;
    }
}

Tidak satu pun dari kode itu yang tahu apakah CarLiabilityantarmuka atau kelas yang konkret. Itu hal yang baik. Itu tidak mau tahu.

Jika itu sebuah antarmuka Carmungkin mengimplementasikannya. Ini tidak akan melanggar ISP karena meskipun isQuality()di Car CarShoptidak bersikeras. Ini baik

Jika ini konkret, itu mungkin karena isQuality()tidak ada atau telah pindah ke tempat lain. Ini baik

Mungkin juga itu CarLiabilityadalah pembungkus konkret Caryang mendelegasikan pekerjaan untuk itu. Selama CarLiabilitytidak mengekspos isQuality()maka CarShopbaik-baik saja. Tentu saja ini hanya menendang kaleng di jalan dan CarLiabilityharus mencari cara untuk mengikuti ISP dengan Carcara yang sama CarShopharus dilakukan.

Singkatnya, isQuality()tidak perlu dihapus dari Carkarena ISP. Kebutuhan tersirat perlu isQuality()dihapus dari CarShopkarena CarShoptidak membutuhkannya, jadi tidak seharusnya memintanya.

candied_orange
sumber
4

Apakah prinsip pemisahan antarmuka berlaku untuk metode konkret?

Tetapi bagaimana dengan metode konkret? Haruskah saya memisahkan metode yang tidak akan digunakan oleh setiap klien?

Tidak juga. Ada berbagai cara untuk menyembunyikan Car.isQualityPassdari CarShop.

1. Akses pengubah

Dari sudut pandang Hukum Demeter , kita dapat mempertimbangkan Cardan CardShoptidak menjadi teman . Itu melegitimasi kita untuk melakukan yang berikutnya.

package com.my.package.domain.model
public class Car{
    ...
    protected boolean isQualityPass(){...}
}

package com.my.package.domain.services
public class CarShop{
    ...
}

Waspadai kedua komponen tersebut dalam paket yang berbeda. Sekarang CarShoptidak memiliki visibilitas atas perilaku yang Car dilindungi . (Maafkan saya sebelumnya jika contoh di atas terlihat sangat sederhana).

2. Segregasi Antarmuka

The ISP bekerja dari premis bahwa kita bekerja dengan abstraksi, tidak dengan kelas beton. Saya akan berasumsi bahwa Anda sudah terbiasa dengan implementasi ISP dan dengan antarmuka peran .

Meskipun Carimplementasi yang sebenarnya , tidak ada yang menghentikan kita dari mempraktikkan ISP.

//role interfaces 
public interface Billable{
   public int getCosts();
   public int getTaxs();
}

//role interfaces
public interface QualityAssurance{
   public boolean isQualityPass();
}

public class Car implements Billable, QualityAssurance{
   ...
}

public class CarShop {
  ...
  public int getPrice(Billable billable){
     return billable.getCosts() * billable.getTaxs();
  }
}

Apa yang saya lakukan di sini. Saya telah mempersempit interaksi antara Cardan CarShopmelalui antarmuka peran Billable . Waspadai perubahan pada getPricetanda tangan. Saya telah memodifikasi argumen dengan sengaja. Saya ingin menjelaskan bahwa CarShophanya "terikat / terikat" ke salah satu antarmuka peran yang tersedia. Saya bisa saja mengikuti implementasi yang sebenarnya tetapi saya tidak tahu detail implementasi yang sebenarnya dan saya takut yang sebenarnya getPrice(String carId)memiliki akses (visibilitas) ke kelas yang konkret. Jika sudah, semua pekerjaan yang dilakukan dengan ISP menjadi tidak berguna karena itu ada di tangan pengembang untuk melakukan casting dan hanya bekerja dengan antarmuka Billable . Tidak peduli seberapa metodisnya kita, godaan akan selalu ada.

3. Tanggung jawab tunggal

Saya khawatir saya tidak dalam posisi untuk mengatakan apakah ketergantungan antara Cardan HttpClientmemadai, tapi saya setuju dengan @DocBrown, itu memunculkan beberapa peringatan yang layak review desain. Baik Hukum Demeter maupun ISP tidak akan membuat desain Anda "lebih baik" pada saat ini. Mereka hanya akan menutupi masalah, bukan memperbaikinya.

Saya telah menyarankan DocBrown Pola Strategi sebagai solusi yang mungkin. Saya setuju dengan dia bahwa polanya menambah kompleksitas, tetapi saya juga berpikir bahwa desain ulang akan. Ini adalah trade-off, semakin banyak decoupling yang kita inginkan, semakin banyak bagian bergerak yang kita miliki (biasanya). Bagaimanapun, saya pikir keduanya setuju dengan desain ulang sangat disarankan.

Menyimpulkan

Tidak, Anda tidak perlu memindahkan metode konkret ke kelas luar agar tidak membuatnya dapat diakses. Mungkin ada banyak konsumen. Apakah Anda akan memindahkan semua metode konkret ke kelas luar setiap kali konsumen baru ikut bermain? Saya harap kamu tidak.

Laiv
sumber