I 3 pilastri della OOP sono: Encapsulation (Incapsulamento), Inheritance (Ereditarietà), Polymorphism (Polimorfismo) |
|
Encapsulation (Incapsulamento) è la capacità di un oggetto di contenere/incapsulare al suo interno tutte le informazioni necessarie a se stesso. Possono essere informazioni public (accessibili anche da oggetti esterni), o private (accessibili solo a se stesso) o protected (accessibili a se stesso e a chi deriva da se stesso).
|
|
Inheritance (Ereditarietà) è il modo di definire funzionalità comuni di una classe che possono essere definite nei padri ed essere ereditate dalle classi figli. L'ereditarietà preserva sempre l'Incapsulamento. Una classe derivata definisce i suoi membri, ma non potrà mai accedere ai membri privati della classe base (per fare ciò i membri devono essere protected. I membri privati sono accessibili SOLO dalle classi che li definiscono. Ogni classe può avere solo 1 classe base/padre, mentre possono derivare da più Interface. |
Relazioni IS-A e HAS-A |
Una relazione IS-A è una relazione di dipendenza. Le nuove classi possono essere create utilizzando altre classi come punto di partenza. Il ruolo della classe base è principalmente quello di definire tutti i dati ed i membri comuni della classe che la estenderanno. Le classi estese vengono chiamate derivate o figlie. I costruttori vengono definiti public, una classe derivata non eredita mai il costruttore della classe padre. |
La parola chiave sealed rende una classe non ereditabile da nessuna altra classe. Solitamente una classe SEALED è è una classe di tipo utility. |
La parola chiave protected definisce membri che possono essere accessibili dalle classi derivate (ma non dalle classi esterne). I vantaggi di definire dei membri protected sono che le classi derivate non devono definire nuovamente questi membri e non devono accedervi con metodi pubblici. Quando si definisce un membro protected si crea un livello di sicurezza tra padre/figlio ed il compilatore non intercetta nessuna violazione di regole di business. Dal momento che i membri protected potrebbero rompere le regole di incapsulamento, è buona norma definire dei metodi protected. Nelle classi padre è prassi comune definire un set di metodi che possono essere utilizzati solo dalla classi ereditate e non di pubblico accesso. |
La parola chiave base si riferisce alla classe da cui la classe ha ereditato. Per ottimizzare la creazione di classi derivate è buona cosa implementare il costruttore della sub-class perchè chiami espressamente il costruttore appropriato della classe base anziché quello di default. In questo modo si è in grado di ridurre il numero di chiamate per ereditare l'inizializzazione dei membri (e risparmiare tempo di processo):
public class myBaseClass
{
public string myName { get; set;}
public string myCountry { get; set;}
public myBaseClass(string name, string country)
{
this.myName = name;
this.myCountry = country;
}
}
public class myClass : myBaseClass
{
public string myAka { get; set; }
public myClass(string name, string country, string aka)
: base(name, country)
{
this.myAka = aka;
}
Si deve usare la parola chiave base ogni volta che una classe figlia desidera accedere ad un membro public o protected definito nella classe padre, non è limitato solo alla logica del costruttore.
Una volta che si aggiunge un costruttore personalizzato nella definizione di una classe, il costruttore default è tacitamente rimosso, quindi bisogna assicurarsi di ridefinire il costruttore default se lo si vuole utilizzare.
|
Una relazione HAS-A è conosciuta anche come contenimento/delega. Questa relazione è semplicemente l'azione di aggiungere membri pubblici alla classe contenitore perchè possa utilizzare le funzionalità dell'oggetto contenuto:
public class aClass
{
public string fieldA { get; set;}
public string fieldB { get; set;}
}
public class bClass
{
public aClass AClass { get; set; } = new aClass()
public string getFieldA()
{
string result = string.empty;
if (this.AClass != null)
{
result = this.AClass.fieldA;
}
return result;
}
}
|
I tipi nested (nidificati) sono una parte di oggetti di relazione HAS-A. In C# è possibile definire un tipo (enum, class, interface, struct, delegate) direttamente all'interno della class o struct. Quando si fa questo l'oggetto innestato è considerato un membro della classe innestante e può essere manipolato come qualunque altro membro.
public class aClass
{
public class subClassA { }
public class subClassB { }
}
- Gli oggetti nested permettono un pieno controllo sull'accesso dei tipi contenuti e possono essere dichiarati private.
- Essendo membri di una class, possono accedere ai membri private della class.
- Spesso un tipo innestato è utile solo per la class contenente, quindi può essere dichiarato private e non renderlo accessibile all'esterno della class.
|
|
Polymorphism (Polimorfismo) è un modo per cui una sub-class possa definire la sua versione di un metodo definito dalla sua classe base, utilizzando il processo chiamato overriding.
I delegati vengono utilizzati per passare metodi come argomenti ad altri metodi.
I gestori di evento non sono altro che metodi richiamati tramite delegati.
Creare un metodo personalizzato e una classe, ad esempio un controllo Windows, che può chiamare tale metodo quando si verifica un determinato evento.
|
virtual e override |
Se in una classe base si vuole definire un metodo che potrebbe essere sovrascritto (ma non è obbligatorio sovrascriverlo), allora deve essere segnato con la parola chiave virtual. I metodi segnati con la parola virtual vengono definiti metodi virtuali.
Quando una sub-class vuole modificare i dettagli di implementazione di un metodo virtuale, deve utilizzare la parola override.
public class aClass
{
public virtual void DoSomething(int total)
{
int myTotal = total * 2;
}
}
public class bClass : aClass
{
public override void DoSomething(int total)
{
int myTotal = total * 5;
}
}
Ogni metodo overrided può richiamare il metodo virtual mediante la parola base, in questo modo non è necessario riscrivere tutta la logica del metodo base, ma lo si può implementare (e possibilmente estendere).
|
Classi Abstract (astratte) |
Una class abstract è una class che non piò essere direttamente istanziata ma deve può solo fare da classe base per classi derivanti. Questo per prevenire che si possa creare un oggetto della classe base.
public abstract class abstractClass
{ }
// ERROR!!!! Impossibile creare un'istanza di una classe abstract!!!
abstractClass absClass = new abstractClass()
A prima vista può sembrare strano creare una class che non si possa istanziare, ma rammentare che le class base (abstract e non) sono molto utili e possono contenere i dati in modo strutturato. Inoltre, utilizzando questa forma di astrazione, si è in grado di creare modelli di "idee" senza creare oggetti concreti. Inoltre non potendo istanziare direttamente abstract class, queste rimangono in memoria quando le classi derivate vengono create; poi è correttissimo definire qualunque costruttore che venga richiamato quando le classi derivate vengono allocate. |
Interface polymorphic (interfaccia polimorfica) |
Quando una classe è definita come base abstract può definire qualunque numero di membri astratti che possono essere utilizzati ovunque si voglia definire un membro che non fornisca un'implementazione di default, ma deve essere definito da ogni classe derivata. Per trasformare una abstract class in polymorphic interface bisogna semplicemente referenziare i suoi metodi astratti e virtuali. Questo approccio è molto più interessante di quanto possa apparire perché permette di costruire facilmente applicazioni estensibili e flessibili.
// The abstract base class of the hierarchy.
abstract class Shape
{
public Shape(string name = "NoName"){ PetName = name; }
public string PetName { get; set; }
// A single virtual method.
public virtual void Draw()
{
Console.WriteLine("Inside Shape.Draw()");
}
}
// Circle DOES NOT override Draw().
class Circle : Shape
{
public Circle() {}
public Circle(string name) : base(name){}
}
// Hexagon DOES override Draw().
class Hexagon : Shape
{
public Hexagon() {}
public Hexagon(string name) : base(name){}
public override void Draw()
{
Console.WriteLine("Drawing {0} the Hexagon", PetName);
}
}
static void Main(string[] args)
{
Console.WriteLine("***** Fun with Polymorphism *****\n");
Hexagon hex = new Hexagon("Beth");
hex.Draw();
Circle cir = new Circle("Cindy");
// Calls base class implementation!
cir.Draw();
Console.ReadLine();
&nsbp;}
Naturalmente bisognerebbe fare in modo che tutti metodi siano obbligati ad implementare il proprio metodo "Draw" e per fare questo basta definire il metodo Draw della classe base come abstract.
I metodi possono essere definiti abstract solo all'interno di classi abstract, altrimenti sarà sollevato un errore!!!!!
abstract class Shape
{
// Forzare la definizione del metodo Draw.
public abstract virtual void Draw();
}
I metodi definiti come abstract sono puramente protocollari. Semplicemente definiscono il nome, il tipo di ritorno (se necessario), e la firma (se richiesta).
Nell'esempio il metodo della classe Shape, informa le classi derivate che "esiste il metodo chiamato "Draw()" che non ritorna nulla e non ha argomenti, se derivi da me, definisci i tuoi dettagli. In questo modo siamo obbligati a sovrascrivere il metodo Draw() nella classe Circle.
|
Member Shadowing (oscuramento di membri) |
Al contrario della sovrascrittura esiste l'oscuramento (shadowing). Se una class derivata definisce un metodo che è identico ad un membro definito nella classe base, la classe derivata ha oscurato la versione del padre. |
|