Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Visitor #16

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open

Visitor #16

wants to merge 6 commits into from

Conversation

xsdc
Copy link
Owner

@xsdc xsdc commented Nov 11, 2024

No description provided.

- For example, for a shooter game, you can add a new operation to the game objects to calculate the damage they receive when hit by a bullet.
- This can be done without modifying the game objects themselves.
- The Visitor pattern enables adding functionality to existing objects without modifying them.
- A visitor is created separately, where the logic added.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Separately from what? And I think the word "is" is missing.

Comment on lines 17 to 22
## Illustrative example

- Consider an analytics service that tracks site traffic for each day of the year.
- We're presented with a log class that contains last years site traffic for each day in an array.
- We can implement a visitor to perform calculations using this array, separate from the log class.
- The visitor is given access to the log class, then we are able to calculate the average, total, or maximum traffic for the year.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Placing an illustration before the problem statement doesn't make logical sense. An illustration is difficult to follow if I haven't first been provided context derived from a problem statement.

Comment on lines 26 to 27
- We would like a way of calculating various product discount types without modifying `Product` objects.
- We'll use the Visitor pattern to perform the discount calculations separately from products.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not an adequate problem statement. A problem statement takes this form: "Scenario >> Objective >> Problem.

So something like: "Assume you need to calculate discounts on various products (scenario and objective). These discounts are constantly shifting, so adding methods to our products to return a discount value would require constant editing of these product classes, which violates the Open Closed Principle (problem). The Visitor pattern can help us solve this problem (leads into the actual solution).

- The operation's name and signature identifies the class that sends the Visit request to the visitor.
- That lets the visitor determine the concrete class of the element being visited.
- Then the visitor can access the element directly through its particular interface.
- The class we would like to add external functionality to.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You say class here but go on to define a protocol.

Comment on lines 37 to 47
protocol Product {
func accept(visitor: DiscountVisitor)
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a fundamental problem with this protocol name. Will message you about it.

Comment on lines 51 to 61
func accept(visitor: DiscountVisitor) {
visitor.visit(macBook: self)
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So it looks like this is absolutely correct, as GPT provided very similar code when I asked for a tutorial on the Visitor pattern. But if you or any dev submitted this code in a PR I would reject it. Method names should align with what they do. This method both accepts and visits a visitor so should be named: acceptAndVisit(_ visitor: DiscountVisitor).

Comment on lines 72 to 73
func visit(macBook: MacBookProduct)
func visit(vision: VisionProduct)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do these methods form grammatical phrases or prepositional phrases? And given the answer, what is their correct method signature based on Swift API Guidelines?


func accept(visitor: ProductVisitor) {
visitor.visit(macProduct: self)
func getDiscount() -> Double {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does the word "get" add here?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And since the method takes no params it could be a computed property, no?

ObjectStructure:
class EmployeeDiscountVisitor: DiscountVisitor {
private let discountPercentage = 0.5
private var discount: Double = 0.0
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You have one property storing discount. But two methods that can set it. So depending on which visit method was called last, the value will change. That is a sure recipe for a bug. I'm not sure how to fix it, but it needs to be fixed.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the solution is need a dedicated Discount Visitor per product. So EmployeeDiscountVisitorForMac, EmployeeDiscountVisitorForVision. You can't have a generic EmployeeDiscountVisitor because you end up with code that lends itself to bugs.

- Then the visitor can access the element directly through its particular interface.
#### Element:

The protocol that accepts a visitor for the objects we would like to add functionality to.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Protocols don't DO anything other than define behaviour. So saying "thee protocol that accepts a visitor" doesn't make sense.

protocol ProductVisitor {
func accept(visitor: ProductVisitor)
protocol DiscountVisitorAccepting {
func accept(_ visitor: DiscountVisitor)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prove to me from the API guidelines that this is the correct method signature rather than `acceptVisitor(_ visitor: DiscountVisitor).

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not saying it is wrong wrong. But I need a solid argument.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

acceptVisitor is better from the call site

Comment on lines 73 to 74
func acceptAndVisit(_ macBookPro: MacBookProProduct)
func acceptAndVisit(_ visionPro: VisionProProduct)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes no sense. Visitors visit; they don't accept.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Previously I said the accept method on DiscountVisitorAccepting should be acceptAndVisit because that is what the method ends up doing. But in hindsight it would be better to stick to the received pattern.

So acceptVisitor on DiscountVisitorAccepting, and then visitMacBook(_:) or visit(macbook:) on DiscountVisitor.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The naming is good now

Comment on lines 103 to 104
private(set) var macBookProDiscount: Double = 0.0
private(set) var visionProDiscount: Double = 0.0
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why define the type?

Comment on lines 89 to 90
private(set) var macBookProDiscount: Double = 0.0
private(set) var visionProDiscount: Double = 0.0
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are you defining the type here Double if there are default values? Swift can resolve the type for you. You are just adding unnecessary code.

Copy link
Collaborator

@ryanbrear ryanbrear left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See my code example in Basecamp for how we can improve this code.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants