Hi Filis,
The short answer is that preserving object identity is essential
for DCI, and without that you have something that's "DCI-like" but
isn't true to the paradigm. That's not to say that wrapping the
role-player with another object to do something similar to DCI
isn't useful, and in fact there's no problem with it until you
encounter a use case where it matters—at which point it becomes a
big problem.
This is a question that has come up repeatedly and there is an FAQ
entry about it on the fulloo website. I also wanted to point
you to a recent discussion about Java that's quite relevant here
as well:
https://groups.google.com/g/object-composition/c/YM0UNIIx_b8/m/L9nYsmIZBAAJ
On 5/12/24 3:32 PM, Filis Futsarov wrote:
This is the best approach that I have found so far, I've checked all the other solutions but I didn't like them for one reason or another.
Was my PHP implementation one of them? I wonder if you could modify that to work better for you, according to your syntax preferences, and modernizing it etc. It contains a similar use of eval to what you mentioned. I'll admit that it can be inconvenient that every data object that might need to play a role needs to implement the RolePlayer trait, but I think this "reverse wrapper" technique (where the role player object wraps the role) may be the only way to implement true DCI in PHP with reasonable syntax, unless you were to develop a solution using source code transformation.
Personally if I were still regularly working in PHP, I think I would probably prefer a source transformation solution, but that would take more time to develop of course, and it might also make debugging more complicated.
The way most source transformation solutions work is to turn the role methods into methods on the context class for the sake of the compiled code, e.g.:
function SourceAccount_transferTo($self)
{...}
On this group, we refer to such a solution as "injectionless" since you're not injecting the methods to the object.
As a final alternative, you could consider the following linter
(written by Andreas, a member of this group), which uses a manual
coding pattern similar to the above but the linter guides you in
coding in this style:
https://github.com/ciscoheat/dcisniffer
This syntax is a lot less supportive of a DCI way of thinking,
but the linter helps with "mind over matter" :-)
Filis --
You received this message because you are subscribed to the Google Groups "object-composition" group.
To unsubscribe from this group and stop receiving emails from it, send an email to object-composit...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/object-composition/7788e218-b82d-460d-91cc-5ccda63ad104n%40googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/object-composition/878c6f3d-b342-4b98-a21f-bafb040d6428n%40googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/object-composition/e3550086-6d7d-41ba-ae98-6a03110875bdn%40googlegroups.com.
It's been a long time so I don't remember for sure, but I think I might have just added the __destruct() method as a "better-than-nothing" solution to unbind the role methods. The problem with it, as you discovered, is that it will only be called when it's time for PHP to garbage-collect the context instance, so if you call a role method while that context instance is still in scope, removeAllRoles() won't have been called yet.
Your approach of calling a do() method would avoid that problem, but keep in mind that some DCI contexts have more than one public method. The most common case is for contexts to have a single method to perform some use case, but there can also be more stateful contexts which are more likely to have multiple public methods—think nested contexts - here's an example from Egon's pong game. Another example would be implementing a data model type using a Context, e.g. if the Account object in the money transfer example needed to be a Context for some reason (perhaps if it needed to do something a bit more complex where Ledgers was a role).
I don't know if this would be any better or easier for the programmer to use than just having something like your do() method for every context method, but something like this would be possible:
And of course there's the option of just letting the programmer handle cleanup manually; removeAllRoles() is a public method, so you could just call $moneyTransfer->removeAllRoles(); when you're done, but of course that's error-prone because you could forget to do it and nothing would warn you.
Hopefully that gives you some useful food for thought.
To view this discussion on the web visit https://groups.google.com/d/msgid/object-composition/c44af8de-6eeb-483c-a410-06347abe5d25n%40googlegroups.com.
On 16 Jun 2024, at 10.38, Filis Futsarov <filisf...@gmail.com> wrote:Question: should the Role be able to access RolePlayer's private properties? or that should ONLY happen through the required methods?
On 16 Jun 2024, at 12.21, Filis Futsarov <filisf...@gmail.com> wrote:Now that I think about it, this should not only be the case for private properties but also for public properties, right? Especially because of this:> Second, it requires that all Role-players { P } for a given role R each have those properties.
And there's no way to enforce these…
And there's no way to enforce these…
By this I meant an "enforcing mechanism" as we do for the required role methods of the Role. So enforcing data!! 🤯🤣
I think that not doing this is actually one of the nice
features of DCI. It's great to have the flexibility that any
role-player that meets the role-object-contract can play that role
(supporting potential future use cases you haven't thought of
yet), regardless of how the object chooses to internally store or
structure its data.
On 16 Jun 2024, at 12.42, Filis Futsarov <filisf...@gmail.com> wrote:By this I meant an "enforcing mechanism" as we do for the required role methods of the Role. So enforcing data!!
Haha, I don't mind you sharing that message with the group.
And well, in that case, you've learned more since your PHP implementation
Hopefully yes :-)
because you were allowing access to data ! 🤣🤣
I agree that those __get() and __set() methods should be removed. I would say though that there could be __get() and __set() on the data class too and in PHP those would be considered part of its public interface...so the next question is if we would ever want that part of the public interface to be included among the public operations in the role-object contract. I would say the answer is maybe yes in theory (for the sake of a few legitimate use cases), but enabling that in my PHP implementation isn't worth it because it allows loopholes like this:
SourceRole {
function withdraw($amount) {
$this->balance = $this->balance - $amount;
}
}
...
class Account {
public $balance;
}
It's worth noting that the whole getter/setter discussion is a bit
different in PHP than in other languages like C# or Java. PHP has
many faults, including making properties and methods public by
default when you don't have a modifier. But something I think it
got right (or at least is a helpful feature) is that it actually
has more of a distinction between messages and methods than most
other languages, which allows you to start with a simple data
property but not be locked into that. For example, let's say this
was the first version of the Employee class and its usage:
class Employee {
public $salary;
}
$bob = new
Employee();
$bob->salary = 100000;
This isn't a realistic example because $salary should never have
just been a public data property in the first place (it shouldn't
allow zero or negative numbers for one thing), but set that aside
for the moment and imagine that the public $salary property met
all the business requirements of the first version of the app
without any bugs. But after some time passes, there's a new
requirement to add some additional validation. The Employee class
could be changed as follows:
class Employee {
private $salary;
public function __get($key) {
if ($key == 'salary') {
return $this->salary;
}
}
public function __set($key, $val) {
if ($key
== 'salary') {
if ($this->validateSalary($val)) {
$this->salary = $val;
}
}
}
...
}
$bob = new
Employee();
$bob->salary = 100000;
I'm not saying this to teach you about PHP (you clearly already
know this stuff), but so you and the group see what I'm talking
about... notice how the usage ($bob->salary = 100000)
didn't change, so that whether it's a public data property or a
method with some logic in it is just an implementation detail that
the author of the data class can change if/when needed.
So it's not quite as simple as saying that you're "exposing internal data" as soon as you use a public data property in PHP—from the perspective of the consumer, public __get() and __set() methods or even plain old public properties can be considered part of the object's public interface, and if done carefully, they might not violate any encapsulation rules but simply be a means of achieving a more convenient syntax for the consumer, e.g.:
$bob->salary =
100000;
echo $bob->salary;
instead of:
$bob->setSalary(100000);
echo $bob->getSalary();
But at the end of the day, sticking to regular methods only (no
public properties and no magic methods like __get(), __set(), or
__call() ) makes for a role-object contract that's obvious to
everyone and should avoid lots of potential issues.
--
You received this message because you are subscribed to the Google Groups "object-composition" group.
To unsubscribe from this group and stop receiving emails from it, send an email to object-composit...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/object-composition/a7445f22-cc90-4ccc-a8e0-60af0abdf0e8n%40googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/object-composition/6c59670b-8364-4870-848a-f93024bba11f%40gmail.com.
Roles enact parts of use cases.I don’t think I would ever write a business use case where one of the steps was a “get” or a “set.”
I think having a “get” or a “set” anywhere (not just in DCI) is a design smell.
To view this discussion on the web visit https://groups.google.com/d/msgid/object-composition/C5781B6D-30D8-42BE-B08C-C514FA4E5023%40gmail.com.
On 16 Jun 2024, at 21.30, Matthew Browne <mbro...@gmail.com> wrote:I suppose that role methods would usually correspond to use case steps, but we're talking about role *player* methods.
And there's a "get" in many versions of the money transfer example - getBalance (in the Source role).
I think having a “get” or a “set” anywhere (not just in DCI) is a design smell.I agree, if by that you mean that it's usually a sign of a flawed design - but not always. My setSalary example was just the first thing that came to mind, and it's a good example of the design smell you're talking about. That should probably be 'promote' or 'changeSalary'.
But sometimes you need a property to be changeable and it's really just a set...perhaps something like setColor() on a Circle object or setInstructor() on a Course object (the latter is more questionable and would depend on the requirements). Or would you say that those should always be avoided too?
The instructor/course mapping is an association which is an object in itself. Why did you arbitrarily decide to make the instructor a field of the course, rather than the course a field of the instructor? Or should you have both? I think that either way is implementation-think and that the association object is the right answer.