لماذا/متى يجب استخدام الطبقات المتداخلة في .الشبكة ؟ أو لا يجب ؟
سؤال
في كاثلين Dollard مؤخرا بلوق وظيفة, انها مثيرة لاهتمام سبب لاستخدام الطبقات المتداخلة في .صافي.إلا أنها تشير أيضا إلى أن التقدم لا يحب الطبقات المتداخلة.أنا على افتراض أن الناس في الكتابة على التقدم القواعد ليست غبية ، لذلك يجب أن يكون هناك منطق وراء هذا الموقف, لكني لم أكن قادرة على العثور عليه.
المحلول
استخدام فئة متداخلة عند الطبقة كنت التعشيش هو مفيد فقط إلى أرفق الدرجة.فعلى سبيل المثال ، الطبقات المتداخلة تسمح لك أن تكتب شيئا مثل (المبسطة):
public class SortedMap {
private class TreeNode {
TreeNode left;
TreeNode right;
}
}
يمكنك جعل إكمال تعريف الفئة الخاصة بك في مكان واحد, لم يكن لديك للقفز من خلال أي PIMPL الأطواق لتحديد مدى صفك يعمل و العالم الخارجي لا تحتاج إلى أي تنفيذ.
إذا كان TreeNode الطبقة الخارجية ، إما أن تجعل جميع المجالات public
أو جعل حفنة من get/set
طرق استخدامه.العالم الخارجي أن يكون آخر الطبقة الملوثة بهم التحسس الذكي.
نصائح أخرى
من الشمس جافا البرنامج التعليمي:
لماذا استخدام متداخلة الطبقات ؟ هناك عدة أسباب مقنعة باستخدام الطبقات المتداخلة ، من بينها:
- بل هو وسيلة منطقيا تجميع الصفوف التي تستخدم فقط في مكان واحد.
- لأنه يزيد التغليف.
- الطبقات المتداخلة يمكن أن يؤدي إلى المزيد للقراءة كود للصيانة.
تجميع منطقي من الطبقات—إذا كانت الدرجة مفيدة واحدة فقط فئة أخرى ، ثم فمن المنطقي أن تضمين ذلك في الصف والحفاظ على الاثنين معا.التعشيش مثل "مساعد الفئات" يجعل حزمة أكثر تبسيطا.
زيادة التغليف—النظر في اثنين من أعلى مستوى الطبقات A و B حيث B يحتاج إلى الوصول إلى أعضاء لولا ذلك أعلن خاصة.عن طريق إخفاء فئة ب في الدرجة الأولى ، أعضاء يمكن تعريف خاص و ب يمكن الوصول إليها.وبالإضافة إلى ذلك ، ب نفسها يمكن أن تكون مخفية عن العالم الخارجي. <- هذا لا ينطبق C#'s تنفيذ الطبقات المتداخلة ، هذا ينطبق فقط على جافا.
أكثر قابلية للقراءة, كود للصيانة—تداخل فصول صغيرة في أعلى مستوى الطبقات الأماكن رمز أقرب إلى حيث يتم استخدامه.
تماما كسول مؤشر الترابط-الآمن نمط المفرد
public sealed class Singleton
{
Singleton()
{
}
public static Singleton Instance
{
get
{
return Nested.instance;
}
}
class Nested
{
// Explicit static constructor to tell C# compiler
// not to mark type as beforefieldinit
static Nested()
{
}
internal static readonly Singleton instance = new Singleton();
}
}
ذلك يعتمد على الاستخدام.أنا نادرا ما من أي وقت مضى استخدام العامة المتداخلة الدرجة ولكن استخدام خاصة الطبقات المتداخلة كل الوقت.خاصة متداخلة فئة يمكن أن تستخدم الفرعي الكائن الذي يتم استخدامه فقط داخل الأم.مثال على ذلك سيكون إذا HashTable فئة يحتوي على إدخال القطاع الخاص الكائن لتخزين البيانات داخليا فقط.
إذا كان الفصل هو من المفترض أن يتم استخدامها من قبل المتصل (خارجيا) ، عموما أنا مثل يجعلها منفصلة بذاتها الدرجة.
بالإضافة إلى الأسباب الأخرى المذكورة أعلاه, هناك سبب واحد أكثر من أن أستطيع التفكير ليس فقط إلى استخدام الطبقات المتداخلة ، ولكن في الواقع العام الطبقات المتداخلة.بالنسبة لأولئك الذين يعملون مع عامة متعددة الطبقات التي تشترك في نفس نوع عام المعلمات القدرة على إعلان عام مساحة من شأنه أن يكون مفيدا للغاية.للأسف .صافي (أو على الأقل C#) لا يدعم فكرة عامة مساحات الأسماء.وذلك من أجل تحقيق نفس الهدف ، يمكننا استخدام عامة الطبقات لتحقيق نفس الهدف.خذ المثال التالي فئات ذات صلة منطقية الكيان:
public class BaseDataObject
<
tDataObject,
tDataObjectList,
tBusiness,
tDataAccess
>
where tDataObject : BaseDataObject<tDataObject, tDataObjectList, tBusiness, tDataAccess>
where tDataObjectList : BaseDataObjectList<tDataObject, tDataObjectList, tBusiness, tDataAccess>, new()
where tBusiness : IBaseBusiness<tDataObject, tDataObjectList, tBusiness, tDataAccess>
where tDataAccess : IBaseDataAccess<tDataObject, tDataObjectList, tBusiness, tDataAccess>
{
}
public class BaseDataObjectList
<
tDataObject,
tDataObjectList,
tBusiness,
tDataAccess
>
:
CollectionBase<tDataObject>
where tDataObject : BaseDataObject<tDataObject, tDataObjectList, tBusiness, tDataAccess>
where tDataObjectList : BaseDataObjectList<tDataObject, tDataObjectList, tBusiness, tDataAccess>, new()
where tBusiness : IBaseBusiness<tDataObject, tDataObjectList, tBusiness, tDataAccess>
where tDataAccess : IBaseDataAccess<tDataObject, tDataObjectList, tBusiness, tDataAccess>
{
}
public interface IBaseBusiness
<
tDataObject,
tDataObjectList,
tBusiness,
tDataAccess
>
where tDataObject : BaseDataObject<tDataObject, tDataObjectList, tBusiness, tDataAccess>
where tDataObjectList : BaseDataObjectList<tDataObject, tDataObjectList, tBusiness, tDataAccess>, new()
where tBusiness : IBaseBusiness<tDataObject, tDataObjectList, tBusiness, tDataAccess>
where tDataAccess : IBaseDataAccess<tDataObject, tDataObjectList, tBusiness, tDataAccess>
{
}
public interface IBaseDataAccess
<
tDataObject,
tDataObjectList,
tBusiness,
tDataAccess
>
where tDataObject : BaseDataObject<tDataObject, tDataObjectList, tBusiness, tDataAccess>
where tDataObjectList : BaseDataObjectList<tDataObject, tDataObjectList, tBusiness, tDataAccess>, new()
where tBusiness : IBaseBusiness<tDataObject, tDataObjectList, tBusiness, tDataAccess>
where tDataAccess : IBaseDataAccess<tDataObject, tDataObjectList, tBusiness, tDataAccess>
{
}
يمكننا تبسيط التوقيعات من هذه الفئات باستخدام عامة مساحة (تنفذ عبر الطبقات المتداخلة):
public
partial class Entity
<
tDataObject,
tDataObjectList,
tBusiness,
tDataAccess
>
where tDataObject : Entity<tDataObject, tDataObjectList, tBusiness, tDataAccess>.BaseDataObject
where tDataObjectList : Entity<tDataObject, tDataObjectList, tBusiness, tDataAccess>.BaseDataObjectList, new()
where tBusiness : Entity<tDataObject, tDataObjectList, tBusiness, tDataAccess>.IBaseBusiness
where tDataAccess : Entity<tDataObject, tDataObjectList, tBusiness, tDataAccess>.IBaseDataAccess
{
public class BaseDataObject {}
public class BaseDataObjectList : CollectionBase<tDataObject> {}
public interface IBaseBusiness {}
public interface IBaseDataAccess {}
}
ثم من خلال استخدام فئات جزئية كما اقترح إيريك فان Brakel في تعليق سابق ، يمكنك فصل من فصول منفصلة في الملفات متداخلة.أوصي باستخدام Visual Studio امتداد مثل NestIn لدعم تداخل جزئي فئة ملفات.يسمح هذا "الاسم" الطبقة الملفات أيضا تستخدم لتنظيم متداخلة فئة الملفات في مجلد مثل الطريقة.
على سبيل المثال:
الكيان.cs
public
partial class Entity
<
tDataObject,
tDataObjectList,
tBusiness,
tDataAccess
>
where tDataObject : Entity<tDataObject, tDataObjectList, tBusiness, tDataAccess>.BaseDataObject
where tDataObjectList : Entity<tDataObject, tDataObjectList, tBusiness, tDataAccess>.BaseDataObjectList, new()
where tBusiness : Entity<tDataObject, tDataObjectList, tBusiness, tDataAccess>.IBaseBusiness
where tDataAccess : Entity<tDataObject, tDataObjectList, tBusiness, tDataAccess>.IBaseDataAccess
{
}
الكيان.BaseDataObject.cs
partial class Entity<tDataObject, tDataObjectList, tBusiness, tDataAccess>
{
public class BaseDataObject
{
public DataTimeOffset CreatedDateTime { get; set; }
public Guid CreatedById { get; set; }
public Guid Id { get; set; }
public DataTimeOffset LastUpdateDateTime { get; set; }
public Guid LastUpdatedById { get; set; }
public
static
implicit operator tDataObjectList(DataObject dataObject)
{
var returnList = new tDataObjectList();
returnList.Add((tDataObject) this);
return returnList;
}
}
}
الكيان.BaseDataObjectList.cs
partial class Entity<tDataObject, tDataObjectList, tBusiness, tDataAccess>
{
public class BaseDataObjectList : CollectionBase<tDataObject>
{
public tDataObjectList ShallowClone()
{
var returnList = new tDataObjectList();
returnList.AddRange(this);
return returnList;
}
}
}
الكيان.IBaseBusiness.cs
partial class Entity<tDataObject, tDataObjectList, tBusiness, tDataAccess>
{
public interface IBaseBusiness
{
tDataObjectList Load();
void Delete();
void Save(tDataObjectList data);
}
}
الكيان.IBaseDataAccess.cs
partial class Entity<tDataObject, tDataObjectList, tBusiness, tDataAccess>
{
public interface IBaseDataAccess
{
tDataObjectList Load();
void Delete();
void Save(tDataObjectList data);
}
}
الملفات في visual studio "مستكشف الحلول" ثم سيتم تنظيمها على هذا النحو:
Entity.cs
+ Entity.BaseDataObject.cs
+ Entity.BaseDataObjectList.cs
+ Entity.IBaseBusiness.cs
+ Entity.IBaseDataAccess.cs
وسوف تنفذ مساحة عامة كما يلي:
المستخدم.cs
public
partial class User
:
Entity
<
User.DataObject,
User.DataObjectList,
User.IBusiness,
User.IDataAccess
>
{
}
المستخدم.DataObject.cs
partial class User
{
public class DataObject : BaseDataObject
{
public string UserName { get; set; }
public byte[] PasswordHash { get; set; }
public bool AccountIsEnabled { get; set; }
}
}
المستخدم.DataObjectList.cs
partial class User
{
public class DataObjectList : BaseDataObjectList {}
}
المستخدم.IBusiness.cs
partial class User
{
public interface IBusiness : IBaseBusiness {}
}
المستخدم.IDataAccess.cs
partial class User
{
public interface IDataAccess : IBaseDataAccess {}
}
والملفات التي سيتم تنظيمها في "مستكشف الحلول" على النحو التالي:
User.cs
+ User.DataObject.cs
+ User.DataObjectList.cs
+ User.IBusiness.cs
+ User.IDataAccess.cs
ما سبق هو مثال بسيط باستخدام الخارجي فئة عامة مساحة الاسم.لقد بنيت "عامة مساحات الأسماء" التي تحتوي 9 أو أكثر من نوع المعلمات في الماضي.الحاجة إلى الحفاظ على تلك معلمات نوع متزامنة عبر تسعة أنواع جميعا في حاجة إلى معرفة نوع المعلمات كانت شاقة ، وخاصة عند إضافة معلمة جديدة.استخدام مساحات عامة يجعل هذا الرمز الآن أكثر سهولة للقراءة.
إذا فهمت Katheleen المادة على حق, انها تعتزم استخدام متداخلة الفصل أن تكون قادرا على الكتابة SomeEntity.جمع بدلا من EntityCollection< SomeEntity>.في رأيي انها مثيرة للجدل طريقة لتوفير بعض الكتابة.أنا متأكد من أن في العالم الحقيقي تطبيق مجموعات سوف يكون هناك بعض الاختلاف في التنفيذ ، لذا سوف تحتاج إلى إنشاء فئة منفصلة على أي حال.أعتقد أن استخدام اسم الفئة للحد من فئة أخرى نطاق ليست فكرة جيدة.فإنه يلوث التحسس وتعزيز تبعيات بين الطبقات.باستخدام مساحات الأسماء هو المعيار طريقة التحكم في الطبقات نطاق.ومع ذلك أجد أن استخدام الطبقات المتداخلة مثل @hazzen التعليق غير مقبول إلا إذا كان لديك طن من الطبقات المتداخلة التي هي علامة سيئة التصميم.
استخدام آخر بعد ذكر المتداخلة فصول هي الفصل بين أنواع عامة.على سبيل المثال, لنفترض أن أحد يريد أن يكون بعض العامة الأسر الساكنة الطبقات التي يمكن أن تتخذ الأساليب مع أعداد مختلفة من المعلمات ، جنبا إلى جنب مع قيم بعض من تلك المعايير ، وتوليد المندوبين مع عدد أقل من المعلمات.على سبيل المثال ، أحد يرغب في أن يكون أسلوب ثابت والتي يمكن أن تأخذ Action<string, int, double>
وابن String<string, int>
والتي سوف نداء الموردة العمل يمر 3.5 كما double
;واحد قد ترغب في أن يكون أسلوب ثابت والتي يمكن أن تتخذ an Action<string, int, double>
و تسفر عن Action<string>
, مرورا 7
كما int
و 5.3
كما double
.باستخدام عامة الطبقات المتداخلة ، يمكن للمرء أن يرتب أن يكون أسلوب الدعاء يكون شيئا مثل:
MakeDelegate<string,int>.WithParams<double>(theDelegate, 3.5);
MakeDelegate<string>.WithParams<int,double>(theDelegate, 7, 5.3);
أو لأن هذا الأخير في كل أنواع التعبير يمكن أن يستدل على الرغم من تلك السابقة لا يمكن:
MakeDelegate<string,int>.WithParams(theDelegate, 3.5);
MakeDelegate<string>.WithParams(theDelegate, 7, 5.3);
باستخدام تداخل الأنواع العامة يجعل من الممكن أن أقول أي المندوبين تنطبق على أي أجزاء من العام نوع الوصف.
متداخلة الطبقات يمكن استخدامها الاحتياجات التالية:
- تصنيف البيانات
- عندما منطق الطبقة الرئيسية معقدة وكنت أشعر بأن كنت تحتاج إلى كائنات تابعة لإدارة الصف
- عند الدولة و وجود فئة يعتمد بشكل كامل على أرفق الدرجة
أنا غالبا ما تستخدم الطبقات المتداخلة لإخفاء تفاصيل التنفيذ. مثال من إريك ليبرت الجواب هنا:
abstract public class BankAccount
{
private BankAccount() { }
// Now no one else can extend BankAccount because a derived class
// must be able to call a constructor, but all the constructors are
// private!
private sealed class ChequingAccount : BankAccount { ... }
public static BankAccount MakeChequingAccount() { return new ChequingAccount(); }
private sealed class SavingsAccount : BankAccount { ... }
}
هذا النمط يصبح أفضل حتى مع استخدام الأدوية. انظر هذا السؤال لمدة سنتين بارد الأمثلة.لذلك أنا في نهاية المطاف الكتابة
Equality<Person>.CreateComparer(p => p.Id);
بدلا من
new EqualityComparer<Person, int>(p => p.Id);
كما يمكن أن يكون قائمة عامة من Equality<Person>
ولكن ليس EqualityComparer<Person, int>
var l = new List<Equality<Person>>
{
Equality<Person>.CreateComparer(p => p.Id),
Equality<Person>.CreateComparer(p => p.Name)
}
حيث
var l = new List<EqualityComparer<Person, ??>>>
{
new EqualityComparer<Person, int>>(p => p.Id),
new EqualityComparer<Person, string>>(p => p.Name)
}
ليس من الممكن.هذه فائدة متداخلة الدرجة وراثة من الفئة الأصل.
حالة أخرى (من نفس الطبيعة - إخفاء التنفيذ) هو عندما كنت تريد أن تجعل فئة أعضاء (الحقول خصائص الخ) يمكن الوصول إليها إلا عن فئة واحدة:
public class Outer
{
class Inner //private class
{
public int Field; //public field
}
static inner = new Inner { Field = -1 }; // Field is accessible here, but in no other class
}
كما نوفل ذكرت تنفيذ مصنع مجردة نمط هذا الرمز يمكن axtended لتحقيق فئة مجموعات نمط والتي تقوم على نمط مصنع مجردة.
أود أن عش الاستثناءات التي هي فريدة من نوعها إلى فئة واحدة ، أي.تلك التي لم القيت من أي مكان آخر.
على سبيل المثال:
public class MyClass
{
void DoStuff()
{
if (!someArbitraryCondition)
{
// This is the only class from which OhNoException is thrown
throw new OhNoException(
"Oh no! Some arbitrary condition was not satisfied!");
}
// Do other stuff
}
public class OhNoException : Exception
{
// Constructors calling base()
}
}
هذا يساعد على الحفاظ على ملفات المشروع الخاص بك مرتبة وليس كامل من مائة قصير قليلا استثناء الطبقات.
نضع في اعتبارنا أن كنت سوف تحتاج إلى اختبار فئة متداخلة.إذا كان القطاع الخاص ، أنت لن تكون قادرة على اختبار في عزلة.
هل يمكن أن تجعل من الداخلية ، على الرغم من ، بالتزامن مع InternalsVisibleTo
السمة.ومع ذلك ، فإن هذا من شأنه أن يكون نفس جعل حقل خاص الداخلية فقط لأغراض الاختبار ، والتي أعتبرها سيئة الذاتي الوثائق.
لذا قد ترغب فقط في تنفيذ خاصة الطبقات المتداخلة التي تنطوي على تعقيد منخفضة.
نعم في هذه الحالة:
class Join_Operator
{
class Departamento
{
public int idDepto { get; set; }
public string nombreDepto { get; set; }
}
class Empleado
{
public int idDepto { get; set; }
public string nombreEmpleado { get; set; }
}
public void JoinTables()
{
List<Departamento> departamentos = new List<Departamento>();
departamentos.Add(new Departamento { idDepto = 1, nombreDepto = "Arquitectura" });
departamentos.Add(new Departamento { idDepto = 2, nombreDepto = "Programación" });
List<Empleado> empleados = new List<Empleado>();
empleados.Add(new Empleado { idDepto = 1, nombreEmpleado = "John Doe." });
empleados.Add(new Empleado { idDepto = 2, nombreEmpleado = "Jim Bell" });
var joinList = (from e in empleados
join d in departamentos on
e.idDepto equals d.idDepto
select new
{
nombreEmpleado = e.nombreEmpleado,
nombreDepto = d.nombreDepto
});
foreach (var dato in joinList)
{
Console.WriteLine("{0} es empleado del departamento de {1}", dato.nombreEmpleado, dato.nombreDepto);
}
}
}