Apa Desain OO untuk digunakan (apakah ada Pola Desain)?

11

Saya memiliki dua objek yang mewakili 'Bar / Klub' (tempat Anda minum / bersosialisasi).

Dalam satu skenario saya perlu nama bar, alamat, jarak, slogon

Dalam skenario lain saya perlu nama bar, alamat, url situs web, logo

Jadi saya punya dua objek yang mewakili hal yang sama tetapi dengan bidang yang berbeda.

Saya suka menggunakan objek yang tidak dapat diubah, jadi semua bidang diatur dari konstruktor .

Salah satu opsi adalah memiliki dua konstruktor dan membatalkan bidang lainnya yaitu:

class Bar {
     private final String name;
     private final Distance distance;
     private final Url url;

     public Bar(String name, Distance distance){
          this.name = name;
          this.distance = distance;
          this.url = null;
     }

     public Bar(String name, Url url){
          this.name = name;
          this.distance = null;
          this.url = url;
     }

     // getters
}

Saya tidak suka ini karena Anda harus memeriksa nol ketika Anda menggunakan getter

Dalam contoh nyata saya, skenario pertama memiliki 3 bidang dan skenario kedua memiliki sekitar 10, jadi akan sangat menyakitkan memiliki dua konstruktor , jumlah bidang yang harus saya nyatakan nol dan kemudian ketika objek sedang digunakan Anda tidak akan ' t tahu di mana BarAnda menggunakan dan bidang apa yang akan null dan apa yang tidak.

Opsi apa lagi yang saya miliki?

Dua kelas menelepon BarPreviewdan Bar?

Beberapa jenis warisan / antarmuka?

Hal lain yang mengagumkan?

Blundell
sumber
29
Wow, Anda sebenarnya telah menggunakan penggunaan yang sah Barsebagai pengidentifikasi!
Mason Wheeler
1
jika Anda berbagi beberapa properti, satu opsi adalah menerapkan kelas dasar.
Yusubov
1
Saya tidak pernah memikirkan hal itu. Menulis segala jenis kode untuk salon anjing Bar / Foo saya bisa sangat membingungkan.
Erik Reppen
4
@gnat Bagaimana kabar orang. Dari kutipan tautan Anda: You should only ask practical, answerable questions based on actual problems that you face.dan itulah yang terjadi di sini
Blundell

Jawaban:

9

Pikiran saya:

"Bilah", sebagaimana diwakili dalam domain Anda, memiliki semua hal yang mungkin diperlukan di kedua tempat: nama, alamat, URL, logo, slogan, dan "jarak" (Saya menebak dari lokasi pemohon). Karenanya, di domain Anda, harus ada satu kelas "Bilah" yang merupakan sumber data otoritatif untuk satu bilah, di mana pun data akan digunakan nanti. Kelas ini harus bisa diubah, sehingga perubahan data bar dapat dibuat dan disimpan jika perlu.

Namun, Anda memiliki dua tempat di mana data objek Bar ini diperlukan, dan keduanya hanya memerlukan subset (dan Anda tidak ingin data itu diubah). Jawaban yang biasa adalah "objek transfer data" atau DTO; POJO (objek Java polos) yang berisi pengambil properti yang tidak dapat diubah. DTO ini dapat diproduksi dengan memanggil metode pada objek domain Bar utama: "toScenario1DTO ()" dan "toScenario2DTO ()"; hasilnya menjadi DTO terhidrasi (artinya Anda hanya perlu menggunakan konstruktor yang panjang dan rumit di satu tempat).

Jika Anda perlu mengirim data kembali ke kelas domain utama (untuk memperbaruinya; apa gunanya data jika Anda tidak dapat mengubahnya sesuai kebutuhan untuk mencerminkan keadaan dunia nyata saat ini?), Anda dapat membuat salah satu DTO, atau gunakan DTO yang dapat berubah baru, dan serahkan kembali ke kelas Bar menggunakan metode "updateFromDto ()".

EDIT: untuk memberikan contoh:

public class Bar {
     private String name;
     private Address address; 
     private Distance distance;
     private Url url;
     private Image logo;
     private string Slogan;

     public OnlineBarDto ToOnlineDto()
     {
         return new OnlineBarDto(name, address, url, logo);
     }

     public PhysicalBarDto ToPhysicalDto()
     {
         return new PhysicalBarDto(name, address, distance, slogan);
     }

     public void UpdateFromDto(PhysicalBarDto dto)
     {
         //validation logic here, or mixed into assignments

         name = dto.Name;
         address = dto.Address;
         distance = dto.Distance;
         slogan = dto.Slogan;
     }

     public void UpdateFromDto(OnlineBarDto dto)
     {
         //Validate DTO fields before performing assignments

         name = dto.Name;
         address = dto.Address;
         url= dto.Url;
         logo = dto.Logo;
     }

     // getters/setters - As necessary within the model and data access layers;
     // other classes can update the model using DTOs, forcing validation.
}

public class PhysicalBarDto
{
     public final String Name;
     public final Address Address;
     public final Distance Distance;
     public final String Slogan;

     public PhysicalBarDto(string Name, Address address, Distance distance, string slogan) 
     { //set instance fields using parameter fields; you know the drill }
}

public class OnlineBarDto
{
     public final String Name;
     public final Address Address;
     public final Image Logo;
     public final Url Url;

     public OnlineBarDto(string Name, Address address, Url url, Image logo) 
     { //ditto }
}

Kelas Alamat, Jarak dan Url harus dapat diubah sendiri, atau ketika digunakan dalam DTO, kelas tersebut harus diganti dengan rekan yang tidak dapat diubah.

KeithS
sumber
apa singkatan DTO berdiri? Saya tidak mengerti apa yang Anda katakan bisa Anda berikan dalam contoh yang berhasil. fyi Data berasal dari server sehingga setelah salah satu bentuk kelas ini 'terhidrasi', bidang tidak perlu diubah, hanya digunakan untuk menampilkan di UI
Blundell
1
DTO adalah singkatan dari "objek transfer data", dan mengacu pada kelas data struktur yang sangat sederhana yang digunakan untuk memindahkan data dari lapisan domain "kaya" ke lapisan atas seperti UI, tanpa mengekspos lapisan domain aktual ke UI (memungkinkan perubahan harus dibuat ke domain tanpa mempengaruhi UI selama DTO tidak harus berubah).
KeithS
mutabilitas tidak ada hubungannya dengan modifikasi atau ketekunan.
4
@JarrodRoberson - apakah Anda bercanda? Jika kelas tidak dapat diubah (tidak dapat diubah di tempat setelah instantiasi) satu-satunya cara untuk membuat perubahan pada data di lapisan data adalah dengan membangun contoh baru yang mewakili catatan yang sama (PK yang sama) dengan anggota yang berbeda. Meskipun metode "bermutasi" yang menghasilkan contoh baru dapat membuatnya lebih mudah, metode ini masih memiliki pengaruh besar pada modifikasi dan ketekunan.
KeithS
1
@JarrodRoberson Dengarkan komunitas. Anda salah. . Sebenarnya setengah dari komentar di seluruh jawaban ini menunjukkan bahwa kita membutuhkan sekolah dasar OO di sekitar papan - menjijikkan ..
David Cowden
5

Jika Anda hanya peduli pada subset properti, dan Anda ingin memastikan mereka tidak tercampur, buat dua antarmuka, dan gunakan itu untuk berbicara dengan objek dasar Anda.

jmoreno
sumber
1
Anda mengatakan itu tetapi dapatkah Anda memberi contoh menggunakan Barkelas
Blundell
3

The Builder Pola (atau sesuatu yang dekat dengan itu) mungkin penggunaan sini.

Memiliki objek abadi adalah hal yang patut dikagumi, tetapi kenyataannya adalah bahwa dengan Refleksi di Jawa, tidak ada yang benar-benar aman ;-).

Martijn Verburg
sumber
Saya bisa melihat cara HawaiianPizzaBuilderkerjanya karena nilai yang dibutuhkan hardcoded. Namun bagaimana Anda bisa menggunakan pola ini jika nilai diambil dan diteruskan ke konstruktor? The HawaiianPizzaBuildermasih akan memiliki semua getter bahwa SpicyPizzaBuildermemiliki begitu nol adalah mungkin. Kecuali jika Anda menggabungkan ini dengan @Jarrods Null Object Pattern. Contoh kode dengan Barakan menyampaikan maksud Anda
Blundell
+1 Saya menggunakan pembangun dalam kasus-kasus seperti ini, berfungsi seperti pesona - termasuk, tetapi tidak terbatas pada pengaturan default yang wajar alih-alih nol ketika saya menginginkan ini
agas
3

Poin kunci di sini adalah perbedaan antara apa "Bar" itu dan bagaimana Anda menggunakannya dalam konteks satu atau yang lain.

Bar adalah entitas tunggal di dunia nyata (atau dunia artifisial, seperti game), dan hanya SATU contoh objek yang harus mewakili itu. Kapan saja, ketika Anda tidak membuat instance dari segmen kode, tetapi memuatnya dari file konfigurasi atau database, ini akan lebih jelas.

(Untuk menjadi lebih esoteris: setiap instance Bar memiliki siklus hidup yang berbeda dari objek yang mewakilinya saat program Anda berjalan. Bahkan jika Anda memiliki kode sumber yang membuat instance itu, itu berarti bahwa entitas Bar seperti yang dijelaskan, "ada "dalam keadaan tidak aktif dalam kode sumber Anda, dan" terbangun "ketika kode itu benar-benar membuatnya dalam memori ...)

Maaf untuk awal yang panjang, tapi saya harap ini membuat poin saya jelas. Anda memiliki SATU kelas Bar yang memiliki semua atribut yang Anda perlukan, dan satu instance Bar mewakili setiap entitas Bar. Ini benar dalam kode Anda, dan terlepas dari cara Anda ingin melihat contoh yang sama dalam konteks yang berbeda .

Yang terakhir dapat diwakili oleh dua antarmuka yang berbeda , yang berisi metode akses yang diperlukan (getName (), getURL (), getDistance ()), dan kelas Bar harus mengimplementasikan keduanya. (Dan mungkin "jarak" akan berubah menjadi "lokasi", dan getDistance () menjadi perhitungan dari lokasi lain :-))

Tetapi kreasi adalah untuk entitas Bar dan bukan untuk cara Anda ingin menggunakan entitas itu: satu konstruktor, semua bidang.

Diedit: Saya dapat menulis kode! :-)

public interface Place {
  String getName();
  Address getAddress();
}

public interface WebPlace extends Place {
   URL getUrl();
   Image getLogo();
}

public interface PhysicalPlace extends Place {
  Double getDistance();
  Slogon getSlogon();
}

public class Bar implements WebPlace, PhysicalPlace {
  private final String name;
  private final Address address;
  private final URL url;
  private final Image logo;
  private final Double distance;
  private final Slogon slogon;

  public Bar(String name, Address address, URL url, Image logo, Double distance, Slogon slogon) {
    this.name = name;
    this.address = address;
    this.url = url;
    this.logo = logo;
    this.distance = distance;
    this.slogon = slogon;
  }

  public String getName() { return name; }
  public Address getAddress() { return address; }
  public Double getDistance() { return distance; }
  public Slogon getSlogon() { return slogon; }
  public URL getUrl() { return url; }
  public Image getLogo() { return logo; } 
}
Lorand Kedves
sumber
1

Pola yang sesuai

Apa yang Anda cari paling sering disebut sebagai Null Object Pattern. Jika Anda tidak suka namanya, Anda dapat menyebutnya Undefined Value Patternlabel semantik yang sama dan berbeda. Terkadang pola ini disebut Poison Pill Pattern.

Dalam semua kasus ini, Object adalah pengganti atau pengganti Default Valuebukan null. It doesn't replace the semantic ofnull null but makes it easier to work with the data model in a more predictable way becausesekarang seharusnya tidak pernah menjadi keadaan yang valid.

Ini adalah Pola di mana Anda memesan instance khusus dari kelas yang diberikan untuk mewakili nullopsi sebaliknya sebagai a Default Value. Dengan cara ini Anda tidak perlu memeriksa null, Anda dapat memeriksa identitas terhadap NullObjectcontoh yang diketahui . Anda dapat memanggil metode dengan aman dan sejenisnya tanpa khawatir NullPointerExceptions.

Dengan cara ini Anda mengganti nulltugas Anda dengan NullObjectcontoh representatif mereka dan Anda selesai.

Analisis Berorientasi Objek Yang Tepat

Dengan cara ini Anda dapat memiliki kesamaan Interfaceuntuk polimorfisme dan masih memiliki perlindungan dari harus khawatir tentang tidak adanya data dalam implementasi spesifik antarmuka. Jadi beberapa Barmungkin tidak memiliki keberadaan web, dan beberapa mungkin tidak memiliki data lokasi pada saat pembangunan. Null Object Pattermemungkinkan Anda memberikan nilai default untuk masing-masing markerdata yang untuk data yang mengatakan hal yang sama, tidak ada yang disediakan di sini, tanpa harus berurusan dengan memeriksa NullPointerExceptionsemua tempat.

Desain Berorientasi Objek Yang Tepat

Pertama, memiliki abstractimplementasi yang merupakan set super semua atribut yang baik Bardan Clubberbagi.

class abstract Establishment 
{
     private final String name;
     private final Distance distance;
     private final Url url;

     public Bar(final String name, final Distance distance, final Url url)
     {
          this.name = name;
          this.distance = distance;
          this.url = url;
     }

     public Bar(final String name, final Distance distance)
     {
          this(name, distance, Url.UNKOWN_VALUE);
     }

     public Bar(final String name, final Url url)
     {
          this(name, Distance.UNKNOWN_VALUE, url);
     }

     // other code
}

Kemudian Anda bisa mengimplementasikan sub kelas dari Establishmentkelas ini dan menambahkan hanya hal-hal spesifik yang Anda butuhkan untuk masing-masing kelas Bardan Clubyang tidak berlaku untuk yang lain.

Kegigihan

Objek placeholder ini jika dibangun dengan benar dapat disimpan secara transparan dalam database tanpa penanganan khusus juga.

Bukti masa depan

Jika Anda memutuskan untuk melompat pada kereta musik Inversi / Ketergantungan Injeksi nanti, pola ini membuatnya mudah untuk menyuntikkan objek penanda ini juga.


sumber
0

Saya pikir masalahnya adalah Anda tidak memodelkan Bar di salah satu skenario tersebut (Dan Anda memodelkan dua masalah, objek, dll.) Yang berbeda. Jika saya melihat Bar kelas, saya mengharapkan beberapa fungsi yang berkaitan dengan minuman, menu, kursi yang tersedia, dan objek Anda tidak memilikinya. Jika saya melihat perilaku objek Anda, Anda memodelkan informasi tentang suatu pendirian. Bar adalah apa yang Anda gunakan untuk saat ini, tetapi itu bukan perilaku intrinsik yang mereka terapkan. (Dalam konteks lain: Jika Anda memodelkan suatu Pernikahan, Anda akan memiliki dua variabel instan sebagai Istri orang; Suami pribadi; seorang istri adalah peran saat ini yang Anda berikan pada objek itu pada saat itu, tetapi objek tersebut masihlah seorang Pribadi). Saya akan melakukan sesuatu seperti ini:

class EstablishmentInformation {
     private final String name;

     public EstablishmentInformation(String name){
          this.name = name;
     }

     // getters
}

class EstablishmentLocationInformation {
    EstablishmentInformation establishmentInformation;
     private final Distance distance;

     public EstablishmentLocationInformation (String name, Distance distance){
          this.establishmentInformation = new EstablishmentInformation(name)
          this.distance = distance;
     }
}

class EstablishmentWebSiteInformation {
    EstablishmentInformation establishmentInformation;
     private final Url url;

     public EstablishmentWebSiteInformation(String name, Url url){
          this.establishmentInformation = new EstablishmentInformation(name)
          this.url = url;
     }
}
pengguna1494736
sumber
-1

Benar-benar tidak perlu rumit ini. Anda membutuhkan dua jenis objek yang berbeda? Buat dua kelas.

class OnlineBar {
     private final String name;
     private final Url url;
     public OnlineBar(String name, Url url){
          this.name = name;
          this.url = url;
     }

     // ...
}
class PhysicalBar {
     private final String name;
     private final Distance distance;
     public PhysicalBar(String name, Distance distance){
          this.name = name;
          this.distance = distance;
     }
     //...
}

Jika Anda perlu mengoperasikannya secara merata, pertimbangkan untuk menambahkan antarmuka, atau menggunakan refleksi.

DeadMG
sumber
@ David: Oh noes. Mereka memiliki kesamaan, satu anggota data yang sama. KEADAAN DARURAT!
DeadMG
tanpa beberapa antarmuka umum tidak ada polimorfisme dalam solusi ini, tidak satu pun dari kelas-kelas ini dapat diganti dengan yang lain dengan keputusan desain yang buruk ini. Bukannya mereka tidak memiliki superset atribut yang sama, itu adalah bahwa secara default beberapa atribut itu null. Ingat nullberarti tidak adanya data , bukan tidak adanya atribut.
1
@DeadMG Ini sangat mungkin merupakan latihan dalam ide pengelompokan nilai bersama menjadi objek induk .. solusi Anda tidak akan mendapatkan kredit penuh jika Anda mengusulkannya dalam konteks itu.
David Cowden
OP tidak menentukan apa kebutuhan untuk substitusi. Dan seperti yang saya katakan, Anda bisa menambahkan antarmuka atau menggunakan refleksi jika Anda mau.
DeadMG
@DeadMG Tapi refleksi seperti mencoba menulis aplikasi seluler lintas platform menggunakan satu lingkungan - Mungkin berhasil, tetapi itu tidak benar . Hukuman untuk memanggil metode menggunakan refleksi adalah antara 2 dan 50 kali lebih lambat daripada panggilan metode normal. Refleksi bukanlah obat untuk semua ...
David Cowden
-1

Jawaban saya kepada siapa pun dengan jenis masalah ini adalah memecahnya menjadi langkah-langkah yang dapat dikelola .

  1. Pertama hanya buat dua kelas BarOnedan BarTwo(atau panggil mereka berdua Bartetapi dalam paket yang berbeda)
  2. Mulai gunakan objek Anda sebagai kelas terpisah jangan khawatir tentang duplikasi kode untuk saat ini. Anda akan melihat ketika Anda mendapatkan lintas (metode duplikat) dari satu ke yang lain
  3. Anda mungkin menemukan bahwa mereka sama sekali tidak terkait, dan jadi Anda harus bertanya pada diri sendiri apakah mereka berdua benar-benarbar jika tidak mengubah nama kelas yang menyinggung menjadi apa yang diwakilinya
  4. Jika Anda menemukan bidang atau perilaku umum, Anda dapat mengekstrak interfaceatau superclassdengan perilaku umum
  5. Setelah Anda memiliki interfaceatau superclassAnda dapat membuat builderatau factoryuntuk membuat / mengambil objek implementasi Anda

(4 dan 5 adalah tentang jawaban lain dari pertanyaan ini)

Blundell
sumber
-2

Anda memerlukan kelas dasar, misalnya, Lokasi yang memiliki nama dan alamat . Sekarang, Anda memiliki dua Bar kelas dan BarPreview memperpanjang Lokasi kelas dasar . Di setiap kelas Anda menginisialisasi variabel umum kelas super dan kemudian variabel unik Anda:

public class Location {
    protected final String name;
    protected final String address:

    public Location (String locName, String locAddress) {
    name = locName;
    address = locAddress
    }

}

public class Bar extends Location {
    private final int dist;
    private final String slogan;

    public Bar(String barName, String barAddress,
               int distance, String barSlogan) {
    super(locName, locAddress);
    dist = distance;
    slogan = barSlogan;
    }
}

Dan serupa untuk kelas BarPreview ..

Jika itu membuat Anda tidur lebih nyenyak di malam hari, gantilah semua contoh Lokasi dalam kode saya dengan Apa Pun Yang Anda Pikirkan Akan Jadi Nama yang Pantas.

David Cowden
sumber
OP ingin instance tidak dapat diubah , artinya semuanya harus final.
1
Ini bisa bekerja, Anda perlu ke finalbidang @ David. Barseharusnya tidak extend Locationmeskipun itu tidak masuk akal. Mungkin Bar extends BaseBardan BarPreview extends BaseBarnama-nama ini juga tidak terdengar terlalu bagus, saya berharap untuk sesuatu yang lebih elegan
Blundell
@JarrodRoberson Saya hanya membuat sketsa untuknya .. tambahkan saja agar tidak berubah. Itu bukan-otak. Masalah mendasar dengan pertanyaan OP adalah bahwa ia tidak tahu bagaimana memiliki kelas dasar dan dua kelas terpisah yang memperpanjang kelas dasar. Saya hanya merinci itu.
David Cowden
@ Blundell, apa yang sedang Anda bicarakan? Sebuah Bar adalah sebuah Location . Cukup ganti Lokasiku dengan BaseBar Anda dan itu adalah hal yang persis sama .. Anda tahu bahwa ketika satu kelas memperluas kelas yang diperluas tidak harus bernama Base [ClassThatWillExtend] kan?
David Cowden
1
@ Davidvidow, bisakah Anda berhenti mengiklankan jawaban Anda sebagai komentar di bawah setiap jawaban lainnya?
marktani