CS247 Lecture 20
Last Time: Multiple Inheritance
This Time: D in SOLID, Visitor Pattern
Dependency Inversion Principle: High level modules (closer to main) should not depend on low-level modules (simpler tasks)- instead, each should depend on an abstraction.
Abstract classes should not depend on concrete classes.
Example: Microsoft word. Word counter rely on some sort of keyboard input. Breaks dependency inversion principle because counting words is “high-level”, getting input “low-level”.
Another example: Timer, lights/bell that goes off
Apply dependency inversion once ⇒ Timer should no longer rely directly on Bell.
Abstract Responder class shouldn’t depend on concrete Timer. So apply dependency inversion again.
This is simply the observer pattern - where Source
s are subjects, Responder
s are observers.
We could apply dependency inversion again introduce another layer of abstraction between Timer and bell/lights - but at some point, usefulness runs out. Too many layers of abstraction creates needless complexity.
Visitor Design Pattern (AMAZON INTERVIEW QUESTION) Problem: How do we write a method that depends on two polymorphic types?
Example:
We might want different behaviours for each combination of these concrete classes.
We can override this in Rock
and Stick
- but we don’t know which enemy we’re hitting. Same problem exists in reverse, if we implement it inside Enemy
. We don’t know what kind of weapons we are using.
Solution: Visitor Pattern - Combine overloading with overriding.
beStruckBy
is a virtual function → either callingMonster::beStruckBy
orTurtle::beStruckBy
(runtime).- Call
w.strike
on theWeapon&
. Strike is virtual→Rock::Strike
orStick::Strike
(runtime). - Are we calling
Rock::Strike(Turtle&)
orRock::Strike(Monster&)
? We know the type of this. Ifthis
is aTurtle*
→ call turtle version, if this is aMonster*
→ call monster version. (compile time) This achieves double dispatch. Very powerful technique that combines overloading with overriding.
Basically the dynamic type of Weapon
and Enemy
will both decide which exact function will be called.
Double Dispatch
Double dispatch is a way to dynamically determine the operation to perform based on both the runtime types of two objects. In your example, it’s used to determine the correct operation (
strike
) to perform based on the combination of anEnemy
and aWeapon
.Another use of the Visitor Pattern - add behaviour and functionality to classes, without changing the classes themselves.
Compile-time Dispatch
Compile-time Dispatch: The compile-time dispatch is achieved by calling the correct
Weapon
’sstrike
method based on the dynamic type ofthis
. Ifthis
is aTurtle*
, theTurtle
version ofstrike
is called, and if it’s aMonster*
, theMonster
version is called.
Example: Compiler Design
I might want to collect information about my AST. I could add a virtual method in ASTNode, override in concrete classes. … But → lots of changes scattered over many classes. Difficult to understand, harder to implement incrementally.
Instead: provide nodes an accept method, taking in a polymorphic AST Visitor.
Now, we can write different types of visitors to work on the parse tree. For example:
- String Visitor Collect string literals in program, that must go in the DATA segment.
- Scope Visitor Match variables with associated scope.
- CodeGeneration Visitor Generates assembly for each given Node.
Solution improves cohesiveness all code for a particular visiter is located in one class. New visitors are easy to create, and can be made with zero changes to the AST.
Next: CS247 Lecture 21