Bagaimana cara terbaik mengatur file kelas dan antarmuka?

17

Oke .. setelah semua diskusi saya mengubah pertanyaan saya sedikit untuk lebih mencerminkan contoh nyata yang saya hadapi.


Saya memiliki dua kelas ModelOnedan ModelTwo, kelas-kelas ini melakukan jenis fungsi yang serupa tetapi tidak saling berhubungan. Namun saya memiliki kelas ketiga CommonFuncyang berisi beberapa fungsi publik yang diimplementasikan di keduanya ModelOnedan ModelTwodan telah difaktorkan sesuai DRY. Dua model ini dipakai di dalam ModelMainkelas (yang itu sendiri adalah instantiated di tingkat yang lebih tinggi dll - tapi saya berhenti di tingkat ini).

Kontainer IoC yang saya gunakan adalah Microsoft Unity . Saya tidak berpura-pura menjadi ahli di dalamnya, tetapi pemahaman saya tentang hal itu adalah bahwa Anda mendaftarkan tuple antarmuka dan kelas dengan wadah dan ketika Anda menginginkan kelas konkret Anda meminta wadah IoC untuk objek apa pun yang cocok dengan antarmuka tertentu. Ini menyiratkan bahwa untuk setiap objek yang ingin saya instantiate dari Unity, harus ada antarmuka yang cocok. Karena setiap kelas saya melakukan fungsionalitas yang berbeda (dan -tidak tumpang tindih) ini berarti ada rasio 1: 1 antara antarmuka dan kelas 1 . Namun itu tidak berarti bahwa saya dengan kasar menulis sebuah antarmuka untuk setiap kelas yang saya tulis.

Jadi kode bijak saya berakhir dengan 2 :

public interface ICommonFunc 
{ 
}

public interface IModelOne 
{ 
   ICommonFunc Common { get; } 
   .. 
}

public interface IModelTwo
{ 
   ICommonFunc Common { get; } 
   .. 
}

public interface IModelMain 
{ 
  IModelOne One { get; } 
  IModelTwo Two { get; } 
  ..
}

public class CommonFunc : ICommonFunc { .. }

public class ModelOne : IModelOne { .. }

public class ModelTwo : IModelTwo { .. }

public class ModelMain : IModelMain { .. }

Pertanyaannya adalah bagaimana mengatur solusi saya. Haruskah saya menjaga kelas dan antarmuka bersama? Atau haruskah saya menjaga kelas dan antarmuka bersama? MISALNYA:

Opsi 1 - Diorganisasikan berdasarkan nama kelas

MySolution
  |
  |-MyProject
  |   |
      |-Models
      |   |
          |-Common
          |   |
          |   |-CommonFunc.cs
          |   |-ICommonFunc.cs
          |
          |-Main
          |   |
          |   |-IModelMain.cs
          |   |-ModelMain.cs
          |
          |-One
          |   |
          |   |-IModelOne.cs
          |   |-ModelOne.cs
          |
          |-Two
              |
              |-IModelTwo.cs
              |-ModelTwo.cs
              |

Opsi 2 - Diatur berdasarkan fungsionalitas (kebanyakan)

MySolution
  |
  |-MyProject
  |   |
      |-Models
      |   |
          |-Common
          |   |
          |   |-CommonFunc.cs
          |   |-ICommonFunc.cs
          |
          |-IModelMain.cs
          |-IModelOne.cs
          |-IModelTwo.cs
          |-ModelMain.cs
          |-ModelOne.cs
          |-ModelTwo.cs
          |

Opsi 3 - Memisahkan Antarmuka dan Implementasi

MySolution
  |
  |-MyProject
      |
      |-Interfaces
      |   |
      |   |-Models
      |   |   |
      |       |-Common
      |       |   |-ICommonFunc.cs
      |       |
      |       |-IModelMain.cs
      |       |-IModelOne.cs
      |       |-IModelTwo.cs
      |
      |-Classes
          | 
          |-Models
          |   |
              |-Common
              |   |-CommonFunc.cs
              |
              |-ModelMain.cs
              |-ModelOne.cs
              |-ModelTwo.cs
              |

Opsi 4 - Mengambil contoh fungsionalitas lebih lanjut

MySolution
  |
  |-MyProject
  |   |
      |-Models
      |   |
          |-Components
          |   |
          |   |-Common
          |   |   |
          |   |   |-CommonFunc.cs
          |   |   |-ICommonFunc.cs
          |   |   
          |   |-IModelOne.cs
          |   |-IModelTwo.cs
          |   |-ModelOne.cs
          |   |-ModelTwo.cs
          |
          |-IModelMain.cs
          |-ModelMain.cs
          |

Saya agak tidak suka opsi 1 karena nama kelas di jalan. Tetapi karena saya cenderung rasio 1: 1 karena pilihan / penggunaan IoC saya (dan itu mungkin bisa diperdebatkan) ini memiliki keuntungan dalam melihat hubungan antara file.

Opsi 2 menarik bagi saya, tetapi sekarang saya telah mengeruhkan air antara ModelMaindan sub-model.

Opsi 3 berfungsi untuk memisahkan definisi antarmuka dari implementasi, tetapi sekarang saya memiliki jeda buatan ini di nama path.

Opsi 4. Saya mengambil Opsi 2 dan men-tweak untuk memisahkan komponen dari model induk.

Adakah alasan yang baik untuk lebih memilih yang satu daripada yang lain? Atau kemungkinan tata letak lain yang saya lewatkan?


1. Frank membuat komentar yang memiliki rasio 1: 1 kembali ke C ++ hari dari file .h dan .cpp. Saya tahu dari mana dia berasal. Pemahaman saya tentang Persatuan tampaknya menempatkan saya pada sudut ini, tetapi saya juga tidak yakin bagaimana cara keluar darinya jika Anda juga mengikuti pepatah Program to an interface Tapi itu diskusi untuk hari lain.

2. Saya telah meninggalkan detail masing-masing konstruktor objek. Di sinilah wadah IoC menyuntikkan objek sesuai kebutuhan.

Peter M
sumber
1
Ugh. Baunya Anda memiliki rasio antarmuka / kelas 1: 1. Kontainer IoC mana yang Anda gunakan? Ninject menyediakan mekanisme yang tidak memerlukan rasio 1: 1.
RubberDuck
1
@RubberDuck FWIW Saya menggunakan Unity. Saya tidak mengklaim sebagai ahli di dalamnya, tetapi jika kelas saya dirancang dengan baik dengan tanggung jawab tunggal, bagaimana saya tidak berakhir dengan rasio hampir 1: 1?
Peter M
Apakah Anda memerlukan IBase atau basis bisa abstrak? Mengapa memiliki IDerivedOne ketika sudah mengimplementasikan IBase? Anda harus bergantung pada IBase, bukan berasal. Saya tidak tahu tentang Unity, tetapi wadah IOC lainnya memungkinkan Anda melakukan injeksi "sensitif konteks". Pada dasarnya ketika Client1membutuhkan IBase, itu menyediakan Derived1. Ketika Client2membutuhkan IBase, IoC menyediakan a Derived2.
RubberDuck
1
Nah, jika basis abstrak, maka tidak ada alasan untuk memilikinya interface. An interfacebenar-benar hanya kelas abstrak dengan semua anggota virtual.
RubberDuck
1
RubberDuck benar. Antarmuka yang berlebihan hanya mengganggu.
Frank Hileman

Jawaban:

4

Karena antarmuka secara abstrak mirip dengan kelas dasar, gunakan logika yang sama yang akan Anda gunakan untuk kelas dasar. Kelas-kelas yang mengimplementasikan suatu antarmuka sangat terkait dengan antarmuka.

Saya ragu Anda lebih suka direktori yang disebut "Kelas Dasar"; kebanyakan pengembang tidak menginginkannya, atau direktori yang disebut "Antarmuka". Di c #, direktori juga ruang nama secara default, sehingga membingungkan dua kali lipat.

Pendekatan terbaik adalah memikirkan bagaimana Anda akan memecah kelas / antarmuka jika Anda harus meletakkan beberapa ke perpustakaan yang terpisah, dan mengatur ruang nama / direktori dengan cara yang sama. Pedoman desain .net framework memiliki saran namespace yang mungkin bermanfaat.

Frank Hileman
sumber
Saya agak akrab dengan tautan yang Anda berikan, tetapi saya tidak yakin bagaimana hubungannya dengan organisasi file. Sementara VS default untuk jalur nama untuk namespace awal saya tahu bahwa ini adalah pilihan yang sewenang-wenang. Saya juga telah memperbarui 'contoh' kode saya dan kemungkinan tata letak.
Peter M
@PeterM Saya tidak yakin IDE mana yang Anda gunakan, tetapi Visual Studio tidak menggunakan nama direktori untuk secara otomatis menghasilkan deklarasi namespace ketika menambahkan kelas, misalnya. Ini berarti bahwa sementara Anda dapat memiliki perbedaan antara nama direktori dan nama namespace, itu lebih menyakitkan untuk bekerja seperti itu. Jadi kebanyakan orang tidak melakukan itu.
Frank Hileman
Saya menggunakan VS. Dan ya saya tahu sakit. Ini membawa kembali kenangan tata letak file Safe Source Visual.
Peter M
1
@PeterM Berkenaan dengan rasio antarmuka 1: 1 untuk kelas. Beberapa alat memerlukan ini. Saya melihat ini sebagai cacat alat - .net memiliki kemampuan refleksi yang cukup untuk tidak memerlukan pembatasan dalam alat tersebut.
Frank Hileman
1

Saya memang mengambil pendekatan kedua, dengan beberapa folder / namespaces dalam proyek yang kompleks tentunya. Itu berarti bahwa saya de-pasangan antarmuka dari implementasi konkret.

Dalam proyek yang berbeda, Anda mungkin harus mengetahui definisi antarmuka, tetapi sama sekali tidak perlu mengetahui kelas konkret yang mengimplementasikannya - terutama ketika Anda menggunakan wadah IoC. Jadi proyek-proyek lain hanya perlu referensi proyek antarmuka, bukan proyek implementasi. Ini dapat membuat referensi rendah, dan dapat mencegah masalah referensi melingkar.

Bernhard Hiller
sumber
Saya telah merevisi contoh kode saya dan menambahkan beberapa opsi lagi
Peter M
1

Seperti biasa dengan pertanyaan desain apa pun, hal pertama yang harus dilakukan adalah menentukan siapa pengguna Anda. Bagaimana mereka akan menggunakan organisasi Anda?

Apakah mereka pembuat kode yang akan menggunakan kelas Anda? Maka memisahkan antarmuka dari kode mungkin yang terbaik.

Apakah mereka pengelola kode Anda? Maka pertahankan antarmuka dan kelas bersama mungkin yang terbaik.

Duduk dan buat beberapa use case. Maka organisasi terbaik mungkin muncul di hadapan Anda.

shawnhcorey
sumber
-2

Saya pikir lebih baik untuk menyimpan antarmuka di perpustakaan yang terpisah. Pertama, desain semacam ini meningkatkan ketergantungan. Itu sebabnya implementasi antarmuka ini dapat dilakukan oleh bahasa pemrograman lain.

Larissa Savchekoo
sumber