Waarom/wanneer moet jy gebruik geneste klasse in .netto?Of moet jy nie?
Vra
In Kathleen Dollard se onlangse blog post, sy bied'n interessante rede om te gebruik geneste klasse in .netto.Egter, sy noem ook dat FxCop nie soos geneste klasse.Ek is die veronderstelling dat die mense die skryf van FxCop reëls is nie dom nie, so daar moet redenasie agter dat die posisie nie, maar ek het nie in staat was om dit te vind.
Oplossing
Gebruik 'n sub-klas toe die klas jy nes is net nuttig vir die omringende klas. Byvoorbeeld, geneste klasse toelaat om so iets te skryf (vereenvoudig):
public class SortedMap {
private class TreeNode {
TreeNode left;
TreeNode right;
}
}
Jy kan 'n volledige definisie van jou klas te maak in een plek, het jy nie het om te spring deur enige PIMPL hoepels te definieer hoe jou klas werk, en die res van die wêreld het nie nodig om enigiets van jou implementering sien.
As die TreeNode klas was eksterne, jy sal óf moet maak al die velde public
of maak 'n klomp van die get/set
metodes om dit te gebruik. Die res van die wêreld sou 'n ander klas het besoedel hul IntelliSense.
Ander wenke
Hoekom Gebruik Nested Klasse? Daar is verskeie dwingende redes vir die gebruik van geneste klasse, onder hulle:
- Dit is 'n manier om logies te groepeer klasse wat net gebruik word in 'n plek.
- Dit verhoog inkapseling.
- Geneste klasse kan lei tot meer leesbaar en onderhou kode.
logiese indeling van klasse-As 'n klas is nuttig om net een ander klas, dan is dit logies om dit in te sluit in daardie klas en bymekaar te hou die twee. Nes so "helper klasse" maak hul pakket meer vaartbelyn.
Verhoogde inkapseling-Oorweeg twee top-vlak klasse, A en B, waar B benodig toegang tot lede van 'n wat andersins private sou verklaar word. Deur wegkruip klas B binne die klas A, kan 'n se lede private verklaar en B kan hulle toegang. Daarbenewens kan B homself weggesteek van die buitewêreld <-.. Dit geld nie vir implementering van sub-klasse C # 's, dit geld net vir Java
Meer leesbare, onderhou kode-Nesting klein klasse binne top-vlak klasse plaas die kode nader aan waar dit gebruik word.
Ten volle Lazy en draad-veilige Singleton patroon
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();
}
}
Dit hang af van die gebruik. Ek sou selde gebruik 'n Openbare geneste klas, maar gebruik Private geneste klasse al die tyd. 'N Private geneste klas gebruik kan word vir 'n sub-voorwerp wat bedoel is om gebruik te word net binne die ouer. 'N Voorbeeld hiervan sou wees as 'n HashTable klas bevat 'n private Entry voorwerp te stoor intern net.
As die klas is bedoel om gebruik te word deur die oproeper (ekstern), ek gewoonlik soos die maak van dit 'n aparte selfstandige klas.
In bykomend tot die ander bogenoemde redes, daar is nog 'n rede dat ek kan dink nie net aan geneste klasse gebruik, maar in werklikheid openbare geneste klasse. Vir diegene wat werk met verskeie generiese klasse wat dieselfde generiese tipe parameters deel, die vermoë om te verklaar 'n generiese naamruimte sou baie nuttig wees. Ongelukkig Net (of ten minste C #) ondersteun nie die idee van generiese name spaces. So ten einde dieselfde doel te bereik, kan ons generiese klasse gebruik om dieselfde doel te vervul. Neem die volgende voorbeeld klasse wat verband hou met 'n logiese entiteit:
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>
{
}
Ons kan die handtekeninge van hierdie klasse te vereenvoudig deur gebruik te maak van 'n generiese naamruimte (geïmplementeer via geneste klasse):
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 {}
}
Dan, deur middel van die gebruik van gedeeltelike klasse soos voorgestel deur Erik van Brakel in 'n vroeëre opmerking, jy kan die klasse te skei in afsonderlike sub-lêers. Ek beveel die gebruik van 'n Visual Studio uitbreiding soos NestIn te ondersteun nes die gedeeltelike klas lêers. Dit laat die "naamruimte" klas lêers te ook gebruik word om die sub-klas lêers te organiseer in 'n gids soos manier.
Byvoorbeeld:
Entity.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
{
}
Entity.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;
}
}
}
Entity.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;
}
}
}
Entity.IBaseBusiness.cs
partial class Entity<tDataObject, tDataObjectList, tBusiness, tDataAccess>
{
public interface IBaseBusiness
{
tDataObjectList Load();
void Delete();
void Save(tDataObjectList data);
}
}
Entity.IBaseDataAccess.cs
partial class Entity<tDataObject, tDataObjectList, tBusiness, tDataAccess>
{
public interface IBaseDataAccess
{
tDataObjectList Load();
void Delete();
void Save(tDataObjectList data);
}
}
Die lêers in die Visual Studio oplossing ontdekkingsreisiger sou dan georganiseer word as sulks:
Entity.cs
+ Entity.BaseDataObject.cs
+ Entity.BaseDataObjectList.cs
+ Entity.IBaseBusiness.cs
+ Entity.IBaseDataAccess.cs
En jy die generiese naamruimte soos die volgende sal implementeer:
User.cs
public
partial class User
:
Entity
<
User.DataObject,
User.DataObjectList,
User.IBusiness,
User.IDataAccess
>
{
}
User.DataObject.cs
partial class User
{
public class DataObject : BaseDataObject
{
public string UserName { get; set; }
public byte[] PasswordHash { get; set; }
public bool AccountIsEnabled { get; set; }
}
}
User.DataObjectList.cs
partial class User
{
public class DataObjectList : BaseDataObjectList {}
}
User.IBusiness.cs
partial class User
{
public interface IBusiness : IBaseBusiness {}
}
User.IDataAccess.cs
partial class User
{
public interface IDataAccess : IBaseDataAccess {}
}
En die lêers sou soos volg in die oplossing ontdekkingsreisiger georganiseer:
User.cs
+ User.DataObject.cs
+ User.DataObjectList.cs
+ User.IBusiness.cs
+ User.IDataAccess.cs
Die bogenoemde is 'n eenvoudige voorbeeld van die gebruik van 'n buitenste klas as 'n generiese naamruimte. Ek het "generiese name spaces" met 9 of meer tipe parameters in die verlede gebou. Met dié soort parameters gesinchroniseer oor die nege tipes wat al wat nodig is om die tipe parameters weet hou was vervelige, veral wanneer die toevoeging van 'n nuwe parameter. Die gebruik van generiese name spaces maak dat kode veel meer hanteerbare en leesbaar.
As ek verstaan Katheleen se artikel reg, sy stel om te gebruik geneste klas in staat wees om te skryf SomeEntity.Versameling in plaas van EntityCollection< SomeEntity>.In my mening is dit omstrede manier om te spaar jy'n paar te tik.Ek is redelik seker dat in die werklike wêreld aansoek versamelings sal'n verskil in die implementering, so jy sal nodig het om te skep'n aparte klas in elk geval.Ek dink dat die gebruik van die klas naam te beperk ander klas omvang is nie'n goeie idee.Dit besoedel intellisense en versterk afhanklikhede tussen klasse.Met behulp van namespace is'n standaard manier om te beheer klasse omvang.Maar ek vind dat die gebruik van geneste klasse soos in @hazzen kommentaar is aanvaarbaar, tensy jy het ton van geneste klasse wat is'n teken van slegte ontwerp.
Nog 'n gebruik nog nie genoem geneste klasse is die skeiding van generiese tipes. Byvoorbeeld, veronderstel 'n mens wil 'n paar generiese families van statiese klasse wat metodes met verskillende getalle van parameters kan neem, saam met waardes vir 'n paar van daardie parameters, en genereer afgevaardigdes met minder parameters het. Byvoorbeeld, een wil 'n statiese metode wat 'n Action<string, int, double>
kan neem en lewer 'n String<string, int>
wat die verskaf aksie verby 3.5 as die double
sal noem nie; 'n mens kan ook graag 'n statiese metode wat 'n 'n Action<string, int, double>
kan neem en lewer 'n Action<string>
, verby 7
as die int
en 5.3
as die double
het. Die gebruik van generiese geneste klasse, kan 'n mens te reël om die metode aanroepingen wees iets soos:
MakeDelegate<string,int>.WithParams<double>(theDelegate, 3.5);
MakeDelegate<string>.WithParams<int,double>(theDelegate, 7, 5.3);
of nie, want laasgenoemde tipes in elke uitdrukking kan afgelei word, selfs al is die voormalige kinders kan nie:
MakeDelegate<string,int>.WithParams(theDelegate, 3.5);
MakeDelegate<string>.WithParams(theDelegate, 7, 5.3);
Die gebruik van die sub-generiese tipes maak dit moontlik om te sê wat afgevaardigdes van toepassing is op watter dele van die algehele tipe beskrywing.
Die geneste klasse kan gebruik word vir die volgende behoeftes:
- Klassifikasie van die data
- Wanneer die logika van die belangrikste klas is ingewikkeld en jy voel soos jy nodig ondergeskikte voorwerpe om die bestuur van die klas
- Wanneer jy dat die staat en die bestaan van die klas ten volle afhanklik van die omringende klas
Ek gebruik dikwels sub-klasse aan implementering detail weg te steek. 'n Voorbeeld van antwoord Eric Lippert se hier:
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 { ... }
}
Hierdie patroon word nog beter met gebruik van generiese. Sien hierdie vraag vir twee oulike voorbeelde. So ek uiteindelik skryf
Equality<Person>.CreateComparer(p => p.Id);
in plaas van
new EqualityComparer<Person, int>(p => p.Id);
Ook ek kan 'n generiese lys van Equality<Person>
het maar nie EqualityComparer<Person, int>
var l = new List<Equality<Person>>
{
Equality<Person>.CreateComparer(p => p.Id),
Equality<Person>.CreateComparer(p => p.Name)
}
waar as
var l = new List<EqualityComparer<Person, ??>>>
{
new EqualityComparer<Person, int>>(p => p.Id),
new EqualityComparer<Person, string>>(p => p.Name)
}
is nie moontlik nie. Dit is die voordeel van sub-klas erfgename van 'n ouer klas.
Nog 'n geval (van dieselfde aard - wegkruip implementering) is wanneer jy wil maak lede 'n klas se (velde, eienskappe, ens) slegs toeganklik vir 'n enkele klas:
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
}
As nawfal genoemde implementering van Abstract Factory patroon, kan dit-kode word axtended bereik Klas Clusters patroon wat gebaseer is op Abstract Factory patroon.
Ek wil nes uitsonderings wat eie is aan 'n enkele klas, dws is. Diegene wat nog nooit van enige ander plek gegooi.
Byvoorbeeld:
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()
}
}
Dit help om jou projek lêers netjies en nie vol honderd stomp bietjie uitsondering klasse.
Hou in gedagte dat jy nodig het om die sub-klas te toets. As dit is private, sal jy nie in staat wees om dit te toets in isolasie nie.
Jy kan dit interne maak, al is, in samewerking met die InternalsVisibleTo
skryf . Maar dit sou dieselfde wees as 'n private veld interne net vir toetsdoeleindes, wat ek oorweeg slegte self-dokumentasie.
So, wil jy dalk om net te implementeer private geneste klasse waarby lae kompleksiteit.
ja vir hierdie geval:
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);
}
}
}