A typical Shape needs to be drawn on a Canvas. How would business requirements affect the modeling of these objects, e.g. when would it be better for Shape to reference a Canvas to draw itself on the canvas, or should Canvas receive an instance of Shape to render it on itself. This is just an example but you get the point. If I was to ask this question in broader way, how do business requirements affect the way how the OO system is modeled? How would we OO devs arrive at the best decision based on requirements? Maybe you know a good resource or books to delve into this topic.
Business requirements usually don't dictate such low level things. This kind of decisions are dictated mostly by low level implementation details. Always remember important OOP mantra - higher cohesion and looser coupling. Which means that it's better to keep them isolated and not know about each other. This is obvious impossible, as they need to be rendered at least, so reference has to be put on side at least. So you need to consider other pragmatic reasons like "where rendering is initiated", "should deletion of canvas cause deletion of all the associated shapes", "which operations in Shape require knowledge of the *associated* Canvas", "can one Shape be assigned to multiple Canvases", etc. This, by the way, is very similar to DBs "has many" and "belongs to" kind of relationships. Which usually boils down to the semantic and pragmatic question - what information do you need more often? So: - if Shape has to reference owning Canvas, then you have no other choice, but to make Shape know which Canvas it belongs to; example - change of state of Shape needs to send event to the owning Canvas - if Canvas is deletable, then you have no other choice than to make Canvas know about all the contained Shapes Depending on that, you may even end up with having them referencing each other. By default, better option is to make Canvas reference contained Shape. This is purely because of semantic reason. Canvas is a container, composite object. Shapes are primitives. So it's more natural for Canvas to contain refrences to Shapes.
You talk about both of them referencing each other. When is this actually acceptable? Is cyclic dependency a sign of bad design? I started to dig into this because I'm facing this issue of cyclic dependency, but I can not tell if such design decision is considered a bad practice.
This cyclic dependency is nothing similar to circular dependency in graphs or build dependencies. Moreover, it's not actually a "dependency" at all. It's a reference. This is a mechanism to create composition. Objects not only can depend on each other, they also can "know" about each other. It's not bad per se. If Shape needs to send change events to Canvas, then there's no other/sane way you can do this without storing the reference. Same with Canvas - if rendering is triggered from Canvas side, then there's no sane way you can do it without storing all the contained Shapes. It's *minimal required* knowledge to fulfill the design. Why it triggers your? That's because the number of references to collaborators is correlated with tighter coupling. That's good that extra references make you uncomfortable, you feel that it's bad. That's right feeling - if object has too many references to other collaborators, then in most cases it's a sign of low cohesion and/or too tight coupling. But in this specific case, it's not bad, it's unavoidable. You can't build a system of classes with zero references, right?)) Loose coupling doesn't mean *zero* coupling, there will be minimal required coupling anyway. So it all boils down to what is "minimal required". It depends on requirements and design, if object needs to call something by design, then it can't do it without referencing some collaborator.
Yes of course we aim toward low coupling, not zero coupling. I get it. One way to solve the issue that made sense to me was to introduce mediator pattern, a service that would perform the interaction and produce side effect. This way we will reduce coupling but introduce lower cohesion... man, that irks me a lot, probably more than it should. Am I overthinking it? In particular, I have PerformanceCalendar. It has PerformanceCalendarEntrys that store information about Performer and Performance. Now, Performer wants to know their booked performances from the web UI. Should performer store a reference to the calendar, or is service doing it for them is fine? Controller certainly does not care about it. Is there a good rule to adhere to to solve dilemmas like this? Or is it more about what I am more comfortable dealing with?
I arrived at the following conclusion. If by design the concept of the user account becomes meaningless (and the business requirements basically dictates this, or is it functional requirements? still a bit lost on correct terminology), then I should avoid circular relationship. If it just hinders user account's functionality, but does not completely make it useless (user can still be identified and do some other things except looking up their performances), then it is reasonable to both of them to be aware of each other, given I take care of managing their own lifecycles. That would translate into one to many optional relationship in the database. Does that sound right to you?
> One way to solve the issue that made sense to me was to introduce mediator pattern There's no issue. You have just ONE reference under question. Mediator is a perfect solution for problem when there are tons of references, but you don't have a problem - you have just ONE reference. If you replace one reference with mediator, you won't win anything and only overengineer it. > This way we will reduce coupling but introduce lower cohesion... No, it's 99% that you'll get better cohesion with mediator. Unless you put something totally unrelated in mediator of course. > Am I overthinking it? YES))) Simple recommendation - better do several times and fail, and learn on your attempts and failures rather then try to predict everything, design "perfect solution" which you will abandon or do differently anyway because it's impossible to predict things that you have no experience with. Get experience by doing things. And do them iteratively, don't try to do everything at once. Make it simple, make it work first, and then do SMALL iterations, improve, refactor, redesign some small parts.
Regarding Performer and all that. Pick something that you feel is right and do it. You'll see all the advantages and drawbacks along the way or at the end. Then iterate and do better. > Is there a good rule to adhere to to solve dilemmas like this? Or is it more about what I am more comfortable dealing with? Usually for such cases when you have object tree that represents *structure* of your domain, you better not mix it with any logic. Model stays the *model*, no any logic, it just represents relationships and data, i.e. make it a dumb DTO. And all the logic lives separately. And if you find later that there is some other form of object tree for other task, then you create *new* object tree for new discovered case. So for your question > Should performer store a reference to the calendar, or is service doing it for them is fine? Service doing it is not just fine, but much better. I'd simply created *new* separate dto like BookedPerformances if it's really needed, and would put all the logic for retrieving it separately, to not mix it with the model. But that's a general solution. For this particular question, it feels that you don't need a new separate object, you can get away with simple List<Performance>.
Обсуждают сегодня