Baru-baru ini saya berpikir untuk mengamankan beberapa kode saya. Saya ingin tahu bagaimana orang bisa memastikan sebuah objek tidak pernah bisa dibuat secara langsung, tetapi hanya melalui beberapa metode kelas pabrik. Katakanlah saya memiliki beberapa kelas "objek bisnis" dan saya ingin memastikan setiap instance dari kelas ini akan memiliki status internal yang valid. Untuk mencapai ini, saya perlu melakukan beberapa pemeriksaan sebelum membuat objek, mungkin di konstruktornya. Ini semua baik-baik saja sampai saya memutuskan saya ingin membuat cek ini menjadi bagian dari logika bisnis. Jadi, bagaimana cara mengatur objek bisnis agar dapat dibuat hanya melalui beberapa metode di kelas logika bisnis saya tetapi tidak pernah secara langsung? Keinginan alami pertama untuk menggunakan kata kunci "teman" lama yang baik dari C ++ akan gagal dengan C #. Jadi kami membutuhkan opsi lain ...
Mari kita coba beberapa contoh:
public MyBusinessObjectClass
{
public string MyProperty { get; private set; }
public MyBusinessObjectClass (string myProperty)
{
MyProperty = myProperty;
}
}
public MyBusinessLogicClass
{
public MyBusinessObjectClass CreateBusinessObject (string myProperty)
{
// Perform some check on myProperty
if (true /* check is okay */)
return new MyBusinessObjectClass (myProperty);
return null;
}
}
Tidak apa-apa sampai Anda ingat bahwa Anda masih bisa membuat instance MyBusinessObjectClass secara langsung, tanpa memeriksa inputnya. Saya ingin mengecualikan kemungkinan teknis itu sama sekali.
Lantas, apa pendapat masyarakat tentang ini?
sumber
CreateBusinessObject
metode adalah untuk memvalidasi argumen DAN ke (mengembalikan objek valid baru ATAU mengembalikan kesalahan) dalam panggilan metode tunggal ketika pemanggil tidak segera mengetahui apakah argumen valid untuk diteruskan ke konstruktor.Distance
instance dengan argumen negatif harus membuangArgumentOutOfRangeException
misalnya, Anda tidak akan mendapatkan apa-apa dari membuatDistanceFactory
yang melakukan pemeriksaan yang sama. Saya masih tidak melihat apa yang ingin Anda peroleh di sini.Jawaban:
Sepertinya Anda hanya ingin menjalankan beberapa logika bisnis sebelum membuat objek - jadi mengapa Anda tidak membuat metode statis di dalam "BusinessClass" yang melakukan semua pemeriksaan "myProperty" kotor, dan membuat konstruktor pribadi?
public BusinessClass { public string MyProperty { get; private set; } private BusinessClass() { } private BusinessClass(string myProperty) { MyProperty = myProperty; } public static BusinessClass CreateObject(string myProperty) { // Perform some check on myProperty if (/* all ok */) return new BusinessClass(myProperty); return null; } }
Memanggilnya akan sangat mudah:
sumber
Anda dapat membuat konstruktor menjadi pribadi, dan pabrik menjadi tipe bersarang:
public class BusinessObject { private BusinessObject(string property) { } public class Factory { public static BusinessObject CreateBusinessObject(string property) { return new BusinessObject(property); } } }
Ini berfungsi karena tipe bertingkat memiliki akses ke anggota privat dari tipe pelingkupnya. Saya tahu ini agak membatasi, tetapi semoga ini akan membantu ...
sumber
CreateBusinessObject
metode di dalamFactory
kelas bersarang daripada menjadikan metode statis itu metode langsung dariBusinessObject
kelas ... dapatkah Anda mengingat motivasi untuk melakukannya?Build
.)Atau, jika Anda ingin benar-benar mewah, kontrol terbalik: Minta kelas mengembalikan pabrik, dan lengkapi pabrik dengan delegasi yang dapat membuat kelas.
public class BusinessObject { public static BusinessObjectFactory GetFactory() { return new BusinessObjectFactory (p => new BusinessObject (p)); } private BusinessObject(string property) { } } public class BusinessObjectFactory { private Func<string, BusinessObject> _ctorCaller; public BusinessObjectFactory (Func<string, BusinessObject> ctorCaller) { _ctorCaller = ctorCaller; } public BusinessObject CreateBusinessObject(string myProperty) { if (...) return _ctorCaller (myProperty); else return null; } }
:)
sumber
Anda dapat membuat konstruktor di kelas MyBusinessObjectClass internal, dan memindahkannya serta pabrik ke rakitannya sendiri. Sekarang hanya pabrik yang dapat membuat instance kelas.
sumber
Terlepas dari apa yang disarankan Jon, Anda juga bisa menjadikan metode pabrik (termasuk cek) menjadi metode statis BusinessObject di tempat pertama. Kemudian, buat konstruktor privat, dan semua orang akan dipaksa untuk menggunakan metode statis.
public class BusinessObject { public static Create (string myProperty) { if (...) return new BusinessObject (myProperty); else return null; } }
Tetapi pertanyaan sebenarnya adalah - mengapa Anda memiliki persyaratan ini? Apakah boleh memindahkan pabrik atau metode pabrik ke dalam kelas?
sumber
Setelah bertahun-tahun pertanyaan ini diajukan, dan semua jawaban yang saya lihat sayangnya memberi tahu Anda bagaimana Anda harus melakukan kode Anda alih-alih memberikan jawaban langsung. Jawaban sebenarnya yang Anda cari adalah memiliki kelas Anda dengan konstruktor pribadi tetapi instantiator publik, yang berarti bahwa Anda hanya dapat membuat contoh baru dari contoh lain yang sudah ada ... yang hanya tersedia di pabrik:
Antarmuka untuk kelas Anda:
public interface FactoryObject { FactoryObject Instantiate(); }
Kelas Anda:
public class YourClass : FactoryObject { static YourClass() { Factory.RegisterType(new YourClass()); } private YourClass() {} FactoryObject FactoryObject.Instantiate() { return new YourClass(); } }
Dan, akhirnya, pabrik:
public static class Factory { private static List<FactoryObject> knownObjects = new List<FactoryObject>(); public static void RegisterType(FactoryObject obj) { knownObjects.Add(obj); } public static T Instantiate<T>() where T : FactoryObject { var knownObject = knownObjects.Where(x => x.GetType() == typeof(T)); return (T)knownObject.Instantiate(); } }
Kemudian Anda dapat dengan mudah mengubah kode ini jika Anda memerlukan parameter tambahan untuk pembuatan instance atau untuk memproses terlebih dahulu instance yang Anda buat. Dan kode ini akan memungkinkan Anda untuk memaksa instantiation melalui pabrik karena konstruktor kelas bersifat pribadi.
sumber
Namun opsi lain (ringan) adalah membuat metode pabrik statis di kelas BusinessObject dan menjaga konstruktor tetap pribadi.
public class BusinessObject { public static BusinessObject NewBusinessObject(string property) { return new BusinessObject(); } private BusinessObject() { } }
sumber
Jadi, sepertinya yang saya inginkan tidak bisa dilakukan dengan cara yang "murni". Itu selalu semacam "panggilan kembali" ke kelas logika.
Mungkin saya bisa melakukannya dengan cara yang sederhana, cukup buat metode kontraktor di kelas objek terlebih dahulu memanggil kelas logika untuk memeriksa input?
public MyBusinessObjectClass { public string MyProperty { get; private set; } private MyBusinessObjectClass (string myProperty) { MyProperty = myProperty; } pubilc static MyBusinessObjectClass CreateInstance (string myProperty) { if (MyBusinessLogicClass.ValidateBusinessObject (myProperty)) return new MyBusinessObjectClass (myProperty); return null; } } public MyBusinessLogicClass { public static bool ValidateBusinessObject (string myProperty) { // Perform some check on myProperty return CheckResult; } }
Dengan cara ini, objek bisnis tidak dapat dibuat secara langsung dan metode pemeriksaan publik dalam logika bisnis juga tidak akan merugikan.
sumber
Dalam kasus pemisahan yang baik antara antarmuka dan implementasi, pola
protected-constructor-public-initializer memungkinkan solusi yang sangat rapi.
Diberikan objek bisnis:
public interface IBusinessObject { } class BusinessObject : IBusinessObject { public static IBusinessObject New() { return new BusinessObject(); } protected BusinessObject() { ... } }
dan pabrik bisnis:
public interface IBusinessFactory { } class BusinessFactory : IBusinessFactory { public static IBusinessFactory New() { return new BusinessFactory(); } protected BusinessFactory() { ... } }
perubahan berikut ke
BusinessObject.New()
penginisialisasi memberikan solusi:class BusinessObject : IBusinessObject { public static IBusinessObject New(BusinessFactory factory) { ... } ... }
Di sini diperlukan referensi ke pabrik bisnis beton untuk memanggil
BusinessObject.New()
penginisialisasi. Tetapi satu-satunya yang memiliki referensi yang dibutuhkan adalah pabrik bisnis itu sendiri.Kami mendapatkan apa yang kami inginkan: satu-satunya yang bisa berkreasi
BusinessObject
adalahBusinessFactory
.sumber
public static IBusinessObject New(BusinessFactory factory)
akan menaikkan banyak alis jika orang lain mempertahankan kodenya.factory
menjadiasFriend
. Dalam basis kode saya, ini akan ditampilkan sebagaipublic static IBusinessObject New(BusinessFactory asFriend)
public class HandlerFactory: Handler { public IHandler GetHandler() { return base.CreateMe(); } } public interface IHandler { void DoWork(); } public class Handler : IHandler { public void DoWork() { Console.WriteLine("hander doing work"); } protected IHandler CreateMe() { return new Handler(); } protected Handler(){} } public static void Main(string[] args) { // Handler handler = new Handler(); - this will error out! var factory = new HandlerFactory(); var handler = factory.GetHandler(); handler.DoWork(); // this works! }
sumber
Saya akan meletakkan pabrik di rakitan yang sama dengan kelas domain, dan menandai internal konstruktor kelas domain. Dengan cara ini, semua kelas di domain Anda mungkin dapat membuat instance, tetapi Anda tidak yakin untuk melakukannya, bukan? Siapa pun yang menulis kode di luar lapisan domain harus menggunakan pabrik Anda.
public class Person { internal Person() { } } public class PersonFactory { public Person Create() { return new Person(); } }
Namun, saya harus mempertanyakan pendekatan Anda :-)
Saya pikir jika Anda ingin kelas Person Anda menjadi valid saat dibuat, Anda harus meletakkan kode di konstruktor.
public class Person { public Person(string firstName, string lastName) { FirstName = firstName; LastName = lastName; Validate(); } }
sumber
Solusi ini didasarkan pada ide munificents menggunakan token di konstruktor. Selesai dalam jawaban ini pastikan objek hanya dibuat oleh pabrik (C #)
public class BusinessObject { public BusinessObject(object instantiator) { if (instantiator.GetType() != typeof(Factory)) throw new ArgumentException("Instantiator class must be Factory"); } } public class Factory { public BusinessObject CreateBusinessObject() { return new BusinessObject(this); } }
sumber
Berbagai pendekatan dengan pengorbanan yang berbeda telah disebutkan.
Create
metode dan ctor pribadi.Saya ingin mengusulkan pabrik sebagai kelas parsial yang berisi kelas bertingkat privat dengan konstruktor publik. Anda 100% menyembunyikan objek yang sedang dibangun oleh pabrik Anda dan hanya mengekspos apa yang Anda pilih melalui satu atau beberapa antarmuka.
Kasus penggunaan yang saya dengar untuk ini adalah saat Anda ingin melacak 100% instans di pabrik. Desain ini menjamin tidak seorang pun kecuali pabrik yang memiliki akses untuk membuat contoh "bahan kimia" yang ditentukan di "pabrik" dan menghilangkan kebutuhan akan perakitan terpisah untuk mencapainya.
== ChemicalFactory.cs == partial class ChemicalFactory { private ChemicalFactory() {} public interface IChemical { int AtomicNumber { get; } } public static IChemical CreateOxygen() { return new Oxygen(); } } == Oxygen.cs == partial class ChemicalFactory { private class Oxygen : IChemical { public Oxygen() { AtomicNumber = 8; } public int AtomicNumber { get; } } } == Program.cs == class Program { static void Main(string[] args) { var ox = ChemicalFactory.CreateOxygen(); Console.WriteLine(ox.AtomicNumber); } }
sumber
Saya tidak mengerti mengapa Anda ingin memisahkan "logika bisnis" dari "objek bisnis". Ini terdengar seperti distorsi orientasi objek, dan Anda akhirnya akan mengikat diri sendiri dengan mengambil pendekatan itu.
sumber
Saya tidak berpikir ada solusi yang tidak lebih buruk dari masalahnya, semua yang dia di atas memerlukan pabrik statis publik yang IMHO merupakan masalah yang lebih buruk dan tidak akan menghentikan orang yang hanya menelepon pabrik untuk menggunakan objek Anda - itu tidak menyembunyikan apa pun. Paling baik untuk mengekspos antarmuka dan / atau menjaga konstruktor sebagai internal jika Anda bisa, itu adalah perlindungan terbaik karena rakitan adalah kode tepercaya.
Salah satu opsinya adalah memiliki konstruktor statis yang mendaftarkan pabrik di suatu tempat dengan sesuatu seperti kontainer IOC.
sumber
Berikut adalah solusi lain dalam nada "hanya karena Anda bisa tidak berarti Anda harus" ...
Itu memenuhi persyaratan untuk menjaga kerahasiaan konstruktor objek bisnis dan meletakkan logika pabrik di kelas lain. Setelah itu menjadi sedikit samar.
Kelas pabrik memiliki metode statis untuk membuat objek bisnis. Ini berasal dari kelas objek bisnis untuk mengakses metode konstruksi terlindung statis yang memanggil konstruktor pribadi.
Pabrik itu abstrak sehingga Anda tidak dapat benar-benar membuat instance darinya (karena itu juga akan menjadi objek bisnis, jadi itu akan aneh), dan memiliki konstruktor pribadi sehingga kode klien tidak dapat diturunkan darinya.
Apa yang tidak dicegah adalah kode klien juga berasal dari kelas objek bisnis dan memanggil metode konstruksi statis yang dilindungi (tetapi tidak divalidasi). Atau lebih buruk lagi, memanggil konstruktor default terlindungi yang harus kami tambahkan agar kelas pabrik dapat dikompilasi sejak awal. (Yang kebetulan mungkin menjadi masalah dengan pola apa pun yang memisahkan kelas pabrik dari kelas objek bisnis.)
Saya tidak mencoba menyarankan siapa pun yang waras harus melakukan sesuatu seperti ini, tetapi ini adalah latihan yang menarik. FWIW, solusi pilihan saya adalah menggunakan konstruktor internal dan batas perakitan sebagai penjaga.
using System; public class MyBusinessObjectClass { public string MyProperty { get; private set; } private MyBusinessObjectClass(string myProperty) { MyProperty = myProperty; } // Need accesible default constructor, or else MyBusinessObjectFactory declaration will generate: // error CS0122: 'MyBusinessObjectClass.MyBusinessObjectClass(string)' is inaccessible due to its protection level protected MyBusinessObjectClass() { } protected static MyBusinessObjectClass Construct(string myProperty) { return new MyBusinessObjectClass(myProperty); } } public abstract class MyBusinessObjectFactory : MyBusinessObjectClass { public static MyBusinessObjectClass CreateBusinessObject(string myProperty) { // Perform some check on myProperty if (true /* check is okay */) return Construct(myProperty); return null; } private MyBusinessObjectFactory() { } }
sumber
Akan sangat menghargai mendengar beberapa pemikiran tentang solusi ini. Satu-satunya yang dapat membuat 'MyClassPrivilegeKey' adalah pabrik. dan 'MyClass' membutuhkannya di konstruktor. Sehingga menghindari refleksi kontraktor swasta / "registrasi" ke pabrik.
public static class Runnable { public static void Run() { MyClass myClass = MyClassPrivilegeKey.MyClassFactory.GetInstance(); } } public abstract class MyClass { public MyClass(MyClassPrivilegeKey key) { } } public class MyClassA : MyClass { public MyClassA(MyClassPrivilegeKey key) : base(key) { } } public class MyClassB : MyClass { public MyClassB(MyClassPrivilegeKey key) : base(key) { } } public class MyClassPrivilegeKey { private MyClassPrivilegeKey() { } public static class MyClassFactory { private static MyClassPrivilegeKey key = new MyClassPrivilegeKey(); public static MyClass GetInstance() { if (/* some things == */true) { return new MyClassA(key); } else { return new MyClassB(key); } } } }
sumber