Quando esporti un modulo di classe e apri il file in Blocco note, noterai, in alto, una serie di attributi nascosti (il VBE non li visualizza e non espone la funzionalità per ottimizzare la maggior parte di entrambi). Uno di loro è VB_PredeclaredId
:
Attribute VB_PredeclaredId = False
Set a True
, salvare e reimportare il modulo nel progetto VBA.
In alternativa, se si sta utilizzando Rubberduck, si può semplicemente indicare un'annotazione speciale nella parte superiore del modulo:
'@PredeclaredId
Option Explicit
Rubberduck emette un risultato di controllo dicendo che gli attributi del modulo non sono sincronizzati con il modulo annotazioni e con un clic è possibile impostare l'attributo VB_PredeclaredId
senza uscire dal VBE (nota: al momento della stesura questa è ancora una funzionalità sperimentale).
Le classi con un PredeclaredId
hanno una "istanza globale" che si ottiene gratuitamente - esattamente come i moduli UserForm
(esportare un modulo utente, vedrete che il suo attributo predeclaredId è impostato su true).
Un sacco di persone utilizzano semplicemente l'istanza predeclared per memorizzare lo stato. È sbagliato: è come memorizzare lo stato dell'istanza in una classe statica!
Invece, si leva l'istanza predefinita per implementare il metodo factory:
[Employee
classe]
'@PredeclaredId
Option Explicit
Private Type TEmployee
Name As String
Age As Integer
End Type
Private this As TEmployee
Public Function Create(ByVal emplName As String, ByVal emplAge As Integer) As Employee
With New Employee
.Name = emplName
.Age = emplAge
Set Create = .Self 'returns the newly created instance
End With
End Function
Public Property Get Self() As Employee
Set Self = Me
End Property
Public Property Get Name() As String
Name = this.Name
End Property
Public Property Let Name(ByVal value As String)
this.Name = value
End Property
Public Property Get Age() As String
Age = this.Age
End Property
Public Property Let Age(ByVal value As String)
this.Age = value
End Property
Con questo, si può fare questo:
Dim empl As Employee
Set empl = Employee.Create("Johnny", 69)
Employee.Create
sta lavorando dall'istanza predefinita , ovvero è considerato un membro del tipo e invocato solo dall'istanza predefinita.
Il problema è che questo è anche perfettamente legale:
Dim emplFactory As New Employee
Dim empl As Employee
Set empl = emplFactory.Create("Johnny", 69)
E questo fa schifo, perché ora si dispone di un'API di confusione. È possibile utilizzare le annotazioni '@Description
/VB_Description
per documentare l'utilizzo, ma senza Rubberduck non c'è nulla nell'editor che mostri tali informazioni nei siti di chiamata.
Inoltre, i Property Let
membri sono accessibili, quindi l'istanza Employee
è mutabile:
empl.Name = "Booba" ' Johnny no more!
Il trucco è quello di rendere la vostra classe attuare un un'interfaccia che espone solo ciò che deve essere esposto:
[IEmployee
classe]
Option Explicit
Public Property Get Name() As String : End Property
Public Property Get Age() As Integer : End Property
E ora fate Employee
implementareIEmployee
- classe finale potrebbe essere simile a questo:
[Employee
classe]
'@PredeclaredId
Option Explicit
Implements IEmployee
Private Type TEmployee
Name As String
Age As Integer
End Type
Private this As TEmployee
Public Function Create(ByVal emplName As String, ByVal emplAge As Integer) As IEmployee
With New Employee
.Name = emplName
.Age = emplAge
Set Create = .Self 'returns the newly created instance
End With
End Function
Public Property Get Self() As IEmployee
Set Self = Me
End Property
Public Property Get Name() As String
Name = this.Name
End Property
Public Property Let Name(ByVal value As String)
this.Name = value
End Property
Public Property Get Age() As String
Age = this.Age
End Property
Public Property Let Age(ByVal value As String)
this.Age = value
End Property
Private Property Get IEmployee_Name() As String
IEmployee_Name = Name
End Property
Private Property Get IEmployee_Age() As Integer
IEmployee_Age = Age
End Property
Avviso il metodo Create
ora restituisce l'interfaccia, e l'interfaccia non espone i membri Property Let
?Ora il codice chiamante può assomigliare a questo:
Dim empl As IEmployee
Set empl = Employee.Create("Immutable", 42)
E dal momento che il codice del client è scritto contro l'interfaccia, gli unici membri empl
espone sono i membri definiti dall'interfaccia IEmployee
, il che significa che non vede il metodo Create
, né il getter Self
, né alcuno dei mutanti Property Let
: così invece di lavorare con la classe "calcestruzzo" Employee
, il resto del codice può funzionare con l'interfaccia "astratta" IEmployee
e godere di un oggetto polimorfico immutabile.
http://codereview.stackexchange.com/questions/67825/parameterised-constructors –