VBA composition for java-like inheritance

2020-07-23 08:52发布

Expanding a bit on this question VBA inheritance pattern

I'm reproducing a basic inheritance pattern in VBA, but I would like to understand If there is a more efficient and terse way to accomplish that.

Consider this little test-case.

IAnimal.cls

'declaration
Public Sub eat()
End Sub

Public Sub breathe()
End Sub

Animal.cls : the superclass

Implements IAnimal

' method implementation
Private Sub IAnimal_eat()
    Debug.Print "I'm eating something..."
End Sub

Private Sub IAnimal_breathe()
    Debug.Print "I'm brething..."
End Sub

Cat.cls : a subclass of Animal

Private super As IAnimal

Implements IAnimal

Private Sub Class_Initialize()
    Set super = New Animal
End Sub


'#------------------------------------------------
' methods called when object is accessed as an IAnimal implementor. 
' I HAVE TO re-implement all of them also here in the subclass (in java I don't need to. It's sufficient to implement them in the superclass)
Private Sub IAnimal_eat()
    Me.eat
End Sub

Private Sub IAnimal_breathe()
    Me.breathe
End Sub


'#--------------------------------------------------
' subclass-only methods
' To access those methods I MUST DECLARE the object as Cat (Dim tom as Cat)
Public Sub meow()
Debug.Print "meow..."
End Sub


'#------------------------------------------------ 
' superclass methods
' Since I need to declare the cat object as a Cat (see above)
' I'm FORCED TO explicitly re-implement all of the superclass methods,
' even those that I don't need to override
' otherwise I can't access them

'@Override
Public Sub eat()
    Debug.print "I'm  eating a fish!"
End Sub

'I'm forced to re-implement also this method, in order to use it directly on a *Cat* object
'@Unnecessary-Override
Public Sub breathe()
    super.breathe 
End Sub

Test.bas

Sub Main()

    Dim snowball As IAnimal
    Set snowball = New Cat

    Dim tom As Cat
    Set tom = New Cat

    snowball.meow  'ERROR Method or data member not found <---- cannot access the Cat-only method "meow"
    tom.meow  '<--- prints "meow"

    ' creates a Dog, another subclass of Animal
    Dim pluto As Dog
    Set pluto = New Dog

    'create a collection for all my animals
    Dim myPets As Collection
    Set myPets = New Collection

    myPets.Add tom
    myPets.Add pluto

    Call feed(myPets) '<---- prints
                            'I 'm eating a fish
                            'meow...
                            'I 'm eating a bone
                            'woof...

End Sub

' a routine for showing how to manage an heterogeneous collection of animals
Sub feed(animals As Collection)

    For Each a In animals

        a.eat

        If TypeOf a Is Cat Then
            a.meow
        End If

        If TypeOf a Is Dog Then
            a.woof
        End If

    Next

End Sub

By doing this, I can:

  • call the eat() method on the Cat and Dog objects and have the specific Overridden behavior
  • call the subclass-only methods (like meow())
  • pass an heterogeneous collection of Animals to a feed subroutine, that can "safely" call the Animal superclass methods and also trigger specific code based on the Animal subclass

This seems to work but it's cumbersome: imagine you need to implement many Animal subclasses (Dog, Bird, Armadillo, Platypus, Demogorgon,...). The pattern above FORCES YOU TO:

  1. re-implement all the methods of the IAnimal interface on ALL THE SUBCLASSES
  2. re-implement (again) all the methods, to expose them from the subclass, even when override is not necessary. This is required expecially if you want to access ALSO the subclass-only methods.

So the question is: is there a more efficient/concise way to implement this example (and limit code rewriting for every subclass) ?

1条回答
贪生不怕死
2楼-- · 2020-07-23 08:55

tom shouldn't be declared As Cat in the first place; the feed procedure is superfluous:

Sub Main()    
    Dim tom As IAnimal
    Set tom = New Cat    
    tom.eat    
End Sub

Now, in the Cat class, these members shouldn't need to exist:

'Superclass methods --- I have to explicitly override all methods :(
Public Sub eat()
    super.eat 
    Debug.print "...a fish!"
End Sub

In SOLID/OOP, you code against the interface, not the concrete type - that's why tom is an IAnimal and not a Cat. Being accessed through its IAnimal interface, Cat.eat is entirely redundant, and suggests that a Cat does something an IAnimal doesn't do, which violates SOLID principles: suddenly it becomes relevant that an IAnimal is a Cat, and it shouldn't be, because polymorphism allows IAnimal to be anything, and the Liskov Substitution Principle (LSP - the "L" of "SOLID") says any code that works with an IAnimal should be able to work identically regardless of what implementation of that interface it's given.

Abiding by these principles means that no IAnimal implementation should have a copy of IAnimal members on its default interface (e.g. Cat.eat, vs IAnimal.eat), and that entirely removes your point #2:

  1. re-implements (again) all the methods, to expose them from the subclass, even when override is not necessary.

As for point 1...

  1. re-implement all the methods of the IAnimal interface

That's a compiler requirement, and isn't a VBA quirk: be it in Java, C#, or VBA, you can't say "I'm implementing an interface" ...without implementing its members. Of course Java & C# allow for class inheritance, so your base class could say "I'm implementing an interface", implement all the members, and the derived classes would happily inherit them - but then, that's inheritance, not composition anymore.

查看更多
登录 后发表回答