Consider we have three classes which want to collaborate, then, where is the behaviour?
I guess it can only be in one of the three classes or in a fourth one acting than as a procuedural connector-class, which separates the behaviour from the three classes and to its own class.
But also if we don't have this connector class then one of the three ecapsulate the behaviour of the other classes.
Let us have a lawsuit with a judge, an accused person, and an attorney.
Then we ask the judge to make a decision, or we ask the lawsuit itself. It then has to ask the other ones for details to make a decision and maybe to change the state of all of them. For example, the attorney gets now the state that he isn't free anymore.
The question here is inspired from here: Which object should have the method?
There are two solutions, where to put the behaviour.
One solution from Greg Burghardt + Martin Maat, they point to a procedural solution. They put the behaviour to a Controller-Class. Why procedural? Because the behaviour is separated into the Controller-Class from the Buisnessobjects (User and Chatroom)
And there is a theoretical object orientend solution from Robert Bräutigam, putting the behaviour into one business object, into that one which doesn't pull out data from the other collaborating buisness objects.
And now I'm interessted, how that should work, the solution by Robert Bräutigam. The Chatroom User example itself needs - I guess - informations from each other. My example with the lawsuit also from all others, they have to pull out data every time.
In the comment section https://softwareengineering.stackexchange.com/a/418453/347781 :
But what you would say if both sides pull out the data?, ok i put the behaviour to the Chatroom, but the Chatroom pulls then out the data from the user, because it needs to fullfill its algorithm data from both,....... maybe then we have a third party, fourth and so on, The Chatroom would pull out data from all of the others. What kind of benefit i have to put then the behaviour to the Chatroom. On which metric i can decide to put it to the Chatroom, because you said placing the bevahiour to that location, which dont pull out data, but if each one of them pull out data?^^ – Robin Kreuzer
@RobinKreuzer You have to come up with a design in which no objects pull data out of other objects (some rare exceptions apply). That is what object-orientation is at its core. Objects are there to contribute behavior. It is the key factor that makes oo more maintainable than procedural programming. – Robert Bräutigam
I came from the procedural/functional programming world, so i prefer also the Controller solution. But I don't think - as i wrote -, that this is object oriented in the core. I will post a specific question to this in the future also explaining in that, why Controller/Service based programming is pure procedural.
But here I want to know, how that would work, don't pulling data out from each others. Im looking for an example, how that should work, if the behaviour/algorithm needs data from both, how i should do this with my lawsuit, the chat-user example, and how that should work in general? And what kind of rare exceptions Robert Bräutigam means, if softwareprojects uses their core-logic in more 90% in their code in Controllers/Services (this number is not proofen, only a opinion based feeling)
I also want to know, if there is a rare exception, what to do then? Where is the behaviour then?
It seems for me, that the rare exceptions are the main-case we have to deal with daily, and there is no other way than coming up with the procedural way.
I can't see tell one and tell the others simultaneously. It seems to be a intrinsic paradox.
Edit (because there was wishes for a code example):
a few less behaviour data classes (they are not complete):
class User {}
class Chatroom{}
class Rights{}
class ClockService{}
class Command{}
one pre-word to the getRights().isFunnyLevel().getFunnyLevel()
it' some kind of optional there, so don't think too much about it^^
class CommandService
{
//other services
/**
* This method is called by the System/Procedures/Controller sitting in the ConnectionPackage.
*/
public void doAction(User user, String command)
{
final Command commandParsed = this.parseCommand(command);
switch (commandParsed.getType())
{
case "messageToChannel":
Chatroom chatroom = this.chatRoomService.getRoom(commandParsed.getParameter1());
if (user.getRights().isAdmin())
{
chatroom.write(user, commandParsed.getParameter2());
return;
}
if (!chatroom.isEnteredUser(user)) throw new CommandExecption("User not loggeg in this channel", 5007);
if (chatroom.isMuted(user) || user.isMutedForChatroom(chatroom)) throw new CommandExecption("User is muted in that chatRoom", 5007);
//i dont want to let Chatroom act as a big facade
if (chatroom.getModeObject().getSomeDeeperGraphObject().getFoo().getBar() == "XY" && this.clockService().isBetween9and10oClock() && user.getRights().isFunnyUser().getFunnyLevel() == 7 && user.isConnectedWithWebsocket() &&
user.sendMessagesInTheLastHour > 30 && chatroom.recievedTotalMessageInTheLastTwoHours < 70 && chatroom.getUserStat(user).recievedMessageInTheLastTwoHours > 20 /* and so on*/)
{
if (chatroom.owner == user)
{
chatroom.writeKursive(user, commandParsed.getParameter2());
user.incrementMessageCounterToOwnChannel(commandParsed.getParameter2().length());
if (!(user.isUserWithLogorrhoe && this.clockService().after9_30()))
{
user.incrementMessageCounterTotal(commandParsed.getParameter2().length());
}
}
else if (/* some more/other checks*/)
{
chatroom.writeBold(user, commandParsed.getParameter2());
user.incrementMessageCounterTotal(commandParsed.getParameter2().length());
this.mailService.sendMailToAdmin("YIPIE");
}
else
{
chatroom.writeColourful(user, commandParsed.getParameter2());
//here we dont want to increment any messageCounter
if (this.getRights().isFunnyUser().getFunnyLevel() < 10 && user.getXYAttribute = "bar" && channel.getDetails().getHistory().getCreateTime < user.getCreateTime + clockService.getMonthinTime())
{
this.getRights().isFunnyUser().incrementFunnyLevelWithStep(2);
this.clockService.adjustTimeByDecrement2Hours();
if (/*..*/)
{
chatroom.setNiceLevel(5);
}
else
{
chatroom.setNiceLevel(3 + user.getNiceCounter());
user.niceCounterDecrement(2);
}
this.user.muteUserForAllChannelBeginsWith(channel.getName().getFirstTwoLetter());
}
}
}
if (/* */)
{
//...
}
break;
//...
}
}
private Command parseCommand(String Command)
{
//...
}
}
A few notes to this example:
In this example i use a classic procedural style. the service/procedural-method is telled doing its job. One refactoring step would be, to move the code direct into the ChatRoom class; Then this class would be telled, the others would still be asked. Of course i could there also divide and conquer a little bit, don't having all the code in one big method, but in more methods/procedures. But anyway, i'm more interested, how i can get out of that example a perfect object orientend code and not presenting by me a perfect procedural code.
One extra note:
Maybe i want to reuse that big code in the first if(...)
in a parameterized way on different locations in my application. Maybe also in case ban
or join_room
to get there a special treating, if the if(...)
is true. It's easy to make out of that a if(...)
, which itself ask a lot of others, a own procedure/function. So the code-reusability is great here.
But now im interested, how to get a code out of it telling one and telling the others and why it is good to program like that dispensing on code-reusability. Or, i am wrong here in the last point?
Doc Brown wrotes in the comment section in another question (No trivial god-class refactoring) by me the following
[...] (good candidates are those who work with user attributes exclusively, and need almost nothing else) [...]
he means there the methdods which should only work on the member variables of the user (or in other words: Of the object on which we take our focus) itself.
I think code belonging and working only on the object itself is only the 100%-90% = 10% i mentioned earlier in the question here in some other context. And the other 90% (Controler/Service-Code) is the code which needs to interact with different objects the same time.
But as i understood Robert Bräutigam correctly, it should be exactly the other way around.
But i can't see how i should refactor my code to match this criteria.
So i'm interested how you would do the magic (telling one and telling the others),
why you also think so much people prefer the procedural (buisiness-relevant stateless Controller/Service) approach, why it is better to program in a object oriented way, where the behavior and data are encapsulated also with the limitation don't fullfilling the SOlID in particular the S (Single Responsibility)
The thought with the limitation is not by me, but from Robert Bräutigam; and i have to agree with him. Having only one class (one good-class) in your project means no SOlID, but means also no asking the others, because i only tell with myself, but is that the answer to my question?
One last and side note:
If you take a look into my example, you will find different violations on LoD (Law of Demeter), i asked into deep (god-facades could be there the solution?), and i also change the state of that object with a part-information, i get out of that object, because i mixed it with some other state. (see in my example near the end the thing with the niceCounter
)
Edit 2 (to focus more/again the main problem)
Let us concentrate more on the complex if
and it's content in my example than the things around it.
i try it to make it more clear with the the following abstract/pseudo-code example:
if(
something from user &&
something from ChatRoom &&
something from something deep in the user, maybe the color of the users fingernail? &&
something merged from user and Chatroom (the niceLevels for example) &&
something from clock &&
something ....
)
then
{
change some state in the user
change some state in the ChatRoom
change some state in the niceLevels of user and ChatRoom
change maybe the clock?
change something in a third-party-object, which was not in the if-checks (maybe sending a notification/email or something else)
change some state in ....
}
let us take a look on the first part on the if
:
There is a complex if
-question/code which i don't think is mapable to if(constraints of object1)
then call a method in object2
(what Robert Bräutigam shows in the last edit) which itself do there some checks, because we have there 1) more than two objects and 2) actions in the then
-clause after all constraints are valid. Also we have here in the state some merged-state.
To the then
-clause:
it's a transactional unit, all or nothing, if the if
-clause is true.
Also let us vary the if
and then
clause a little bit, like i did in my first edit-code-example, so it is gonna be much more complex. We can have some more if-then-else
, all with asking different domain-objects and all with different transactional then-clauses
Conclusion:
I can't see how you would map this to collaborating objects, which not ask each other (but only do their own stuff)? - But this is my question, how you would do this magic, which i can't see. How you would write such code, which not ask the others with that examples i given here
And why is writing these kind of code (if it exists a way to code like that) better than the procedural one, i present here?
Edit 3 (to transform the eample in edit2 to a lesser abstract one)
Appendix:
if(
user.getMember1() == 2 &&
ChatRoom.getMember1 == 79 &&
user.getMember2().getMember5().getFingernail() == 4 &&
user.getMember7() + ChatRoom.getMember20() ==70 &&
clock.getMember1() == 12
)
then
{
user.setMember1(50);
user.setMember33(40);
chatRoom.setMember90(10);
clock.setMember14(77);
user.getMember5().getMember108().getMember22().setMessageFilter(7);
bathroom.setMember20(55);
}