The Visitor Design Pattern
The Visitor pattern is a behavioral design pattern that allows you to separate an algorithm from an object structure on which it operates. It can be used to perform operations on a group of related objects, without changing the classes of those objects.
Today, we’re going to take a look at the Visitor pattern and how it can be used to keep a city running smoothly. The police department is our trusty Visitor, patrolling the streets and keeping the city’s various elements in check. The Residential areas, Commercial districts, and Industrial zones are the CityElements, ready to be visited and inspected by the law enforcement. With this pattern in place, the police department can focus on their job and leave the management of the city’s elements to the city itself.
So join me, as we delve deeper into the gritty world of cyberpunk coding and the powerful tools at our disposal. Together, we’ll keep the city running like a well-oiled machine and stay one step ahead of the rogue AIs and cybercriminals lurking in the shadows.
interface CityElement { public function accept(CityVisitor $visitor); } class Residential implements CityElement { public function accept(CityVisitor $visitor) { $visitor->visitResidential($this); } // other Residential class methods } class Commercial implements CityElement { public function accept(CityVisitor $visitor) { $visitor->visitCommercial($this); } // other Commercial class methods } class Industrial implements CityElement { public function accept(CityVisitor $visitor) { $visitor->visitIndustrial($this); } // other Industrial class methods } interface CityVisitor { public function visitResidential(Residential $res); public function visitCommercial(Commercial $com); public function visitIndustrial(Industrial $ind); } class PoliceDepartment implements CityVisitor { public function visitResidential(Residential $res) { echo "The police are checking up on the residential area, making sure the cyborgs aren't causing any trouble.\n"; } public function visitCommercial(Commercial $com) { echo "The police are checking the commercial district for any illegal cyberware dealers.\n"; } public function visitIndustrial(Industrial $ind) { echo "The police are investigating a possible illegal cybernetics factory in the industrial area.\n"; } } class City { private $elements; public function __construct() { $this->elements = array(); } public function addElement(CityElement $element) { $this->elements[] = $element; } public function accept(CityVisitor $visitor) { foreach ($this->elements as $element) { $element->accept($visitor); } } } $city = new City(); $city->addElement(new Residential()); $city->addElement(new Commercial()); $city->addElement(new Industrial()); $city->accept(new PoliceDepartment());
But as with any cyberpunk story worth its weight in chrome, there’s always a twist. The SOLID principles come into play, keeping the code flexible and maintainable. The Single Responsibility Principle ensures that each class has only one reason to change, the Open/Closed Principle ensures that new types of city elements can be added without modifying the existing code, the Liskov Substitution Principle ensures that objects of a superclass can be replaced with objects of a subclass without affecting the correctness of the program, the Interface Segregation Principle ensures that classes implement only the methods that are relevant to them, and the Dependency Inversion Principle ensures that we depend on interfaces rather than concrete classes.
interface CityElement { public function accept(CityVisitor $visitor); } interface ResidentialElement extends CityElement{ // other Residential class methods } interface CommercialElement extends CityElement{ // other Commercial class methods } interface IndustrialElement extends CityElement{ // other Industrial class methods } class Residential implements ResidentialElement { public function accept(CityVisitor $visitor) { $visitor->visitResidential($this); } } class Commercial implements CommercialElement { public function accept(CityVisitor $visitor) { $visitor->visitCommercial($this); } } class Industrial implements IndustrialElement { public function accept(CityVisitor $visitor) { $visitor->visitIndustrial($this); } } interface CityVisitor { public function visitResidential(ResidentialElement $res); public function visitCommercial(CommercialElement $com); public function visitIndustrial(IndustrialElement $ind); } class PoliceDepartment implements CityVisitor { public function visitResidential(ResidentialElement $res) { echo "The police are checking up on the residential area, making sure the cyborgs aren't causing any trouble.\n"; } public function visitCommercial(CommercialElement $com) { echo "The police are checking the commercial district for any illegal cyberware dealers.\n"; } public function visitIndustrial(IndustrialElement $ind) { echo "The police are investigating a possible illegal cybernetics factory in the industrial area.\n"; } } class City { private $elements; public function __construct() { $this->elements = array(); } public function addElement(CityElement $element) { $this->elements[] = $element; } public function accept(CityVisitor $visitor) { foreach ($this->elements as $element) { $element->accept($visitor); } } } $city = new City(); $city->addElement(new Residential()); $city->addElement(new Commercial()); $city->addElement(new Industrial()); $city->accept(new PoliceDepartment());
This code defines three classes representing different parts of a city: Residential, Commercial, and Industrial. Each of these classes implements an accept() method that accepts a visitor object, which can perform operations on the city element. The City class contains a collection of city elements and a method to accept a visitor. The PoliceDepartment class is an example of a visitor that performs different actions on each type of city element. When the accept() method is called on the City object, it iterates through the collection of city elements and calls the accept() method on each one, passing in the visitor object.
In this example the visitor would be the police department and the various city element like Residential, Commercial, and Industrial would be the different parts of the city and the visitXXX methods would represent the police department visiting these parts and doing their job.
- The Single Responsibility Principle (SRP) is followed by dividing the system into small, focused classes that have a single responsibility. Each class only has one reason to change and therefore is less likely to cause unintended side effects when changes are made.
- The Open/Closed Principle (OCP) is followed by keeping the City and CityElement classes open for extension, but closed for modification. New types of city elements can be added without modifying the existing code.
- The Liskov Substitution Principle (LSP) is followed by ensuring that objects of a superclass can be replaced with objects of a subclass without affecting the correctness of the program. The City class accepts any object that implements the CityElement interface, so it can handle any subclasses of CityElement without modification.
- The Interface Segregation Principle (ISP) is followed by creating separate interfaces for Residential, Commercial, and Industrial elements, which have different methods, instead of having one large interface for all types of city elements. This way it allows for classes to implement only the methods that are relevant to them.
- The Dependency Inversion Principle (DIP) is followed by depending on interfaces (ResidentialElement, CommercialElement, IndustrialElement) rather than concrete classes. This makes the code more flexible and easy to add new functionality without changing the existing functionality.
This seems familiar…
You may be thinking, the Visitor pattern, the Template Method pattern, and the Decorator pattern seem very similar. You would be correct, they are all behavioral design patterns, but they have different purposes and use cases.
The Visitor pattern is used to separate an algorithm from an object structure on which it operates. It allows you to perform operations on a group of related objects without changing the classes of those objects. The Visitor pattern can be useful when you have a complex object structure and need to perform various operations on the objects in that structure. The Visitor pattern is used to add new behavior to a group of related classes without changing the classes themselves.
The Template Method pattern is used to define the skeleton of an algorithm in a method, and deferring some steps to subclasses. The subclasses can override the steps as needed, but the overall algorithm remains the same. The Template Method pattern can be useful when you need to implement a common behavior across a group of related classes, but allow for variations in the details of that behavior.
The Decorator pattern is used to add new behavior to an individual object, rather than to an entire class. The Decorator pattern uses object composition to achieve this, rather than inheritance. The Decorator pattern can be useful when you need to add new behavior to individual objects at runtime, without affecting the behavior of other objects.
In summary, the Visitor pattern is used to add new behavior to a group of related classes without changing the classes themselves, the Template Method pattern is used to define the skeleton of an algorithm in a method and let subclasses to override the steps as needed, and the Decorator pattern is used to add new behavior to an individual object at runtime, without affecting the behavior of other objects, by using object composition.