-
-
Notifications
You must be signed in to change notification settings - Fork 45
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
final or not final #54
Comments
@nyamsprod I don't know how the period class is supposed to be used, but it looks like a value object to me.
|
I'm afraid I don't have time to read the original discussion, so maybe
there are actual valid reasons I'm not aware of, but, to be honest, I doubt
it.
I never feel the need to extend or mock Value Objects. I almost never feel
the need to extend _any_ object anymore. All the smart people have been
telling us to use composition over inheritance FOR AT LEAST 2 DECADES.
Inheritance is dead. Get over it.
…On 7 September 2017 at 10:43, Marco Pivetta ***@***.***> wrote:
@nyamsprod <https://github.com/nyamsprod> I don't know how the period
class is supposed to be used, but it looks like a value object to me.
1. values don't need to be "extended" unless a child class adds more
meaning to the value. Specifically, using instanceof against it
provides more context at runtime. Another way to see it is "does it make
sense to have an interface? And would anybody extend it?"
2. if it's not designed to be extended, don't allow for it to be
extended
3. if this is about mocking, then bad news for who is mocking: this is
a value, and it shouldn't be mocked.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#54 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AAd4LHYZIYswBUXrytkbnVAgxdfP5Xh9ks5sf6ylgaJpZM4PPZrd>
.
|
PS If people have use cases that Period doesn't support, the answer is not
to open up Period or bloat it with features. The answer is to fork it and
scratch your own itch.
…On 7 September 2017 at 11:05, Mathias Verraes ***@***.***> wrote:
I'm afraid I don't have time to read the original discussion, so maybe
there are actual valid reasons I'm not aware of, but, to be honest, I doubt
it.
I never feel the need to extend or mock Value Objects. I almost never feel
the need to extend _any_ object anymore. All the smart people have been
telling us to use composition over inheritance FOR AT LEAST 2 DECADES.
Inheritance is dead. Get over it.
On 7 September 2017 at 10:43, Marco Pivetta ***@***.***>
wrote:
> @nyamsprod <https://github.com/nyamsprod> I don't know how the period
> class is supposed to be used, but it looks like a value object to me.
>
> 1. values don't need to be "extended" unless a child class adds more
> meaning to the value. Specifically, using instanceof against it
> provides more context at runtime. Another way to see it is "does it make
> sense to have an interface? And would anybody extend it?"
> 2. if it's not designed to be extended, don't allow for it to be
> extended
> 3. if this is about mocking, then bad news for who is mocking: this
> is a value, and it shouldn't be mocked.
>
> —
> You are receiving this because you were mentioned.
> Reply to this email directly, view it on GitHub
> <#54 (comment)>,
> or mute the thread
> <https://github.com/notifications/unsubscribe-auth/AAd4LHYZIYswBUXrytkbnVAgxdfP5Xh9ks5sf6ylgaJpZM4PPZrd>
> .
>
|
Hello @mathiasverraes, I understand your point, but I think it's too extreme. PHP libraries, open-sourced on github, packaged on packagist, are made for helping the developers lives, not to complicate them. One day I had to fork a library to add some additions of 2 other forks. I could not submit a PR because the library's conception made that I need some hard refactorings to enable the features I needed. Now it's a real mess to maintain. I don't want this to be the rule on software engineering. In my opinion, creating open source libraries on github is to solve common use cases, and not to give a draft to every developer who would have to fork everything to fit his needs. If people have use cases that Period (or any library) does not support, before giving a ready-made "go fuck yourself and click on fork" answer, maybe it should be interesting to ask some questions:
I'm really happy that |
@bpolaszek I get your reasons but they do not invalidate the fact that the class can be made final . If
Then the package author/maintainer will add it. Even if the class is final. The final part will also guarantee less BC breaking. |
> In my opinion, creating open source libraries on github is to solve
> common use cases, and not to give a draft to every developer who would have
> to fork everything to fit his needs.
This is the prevailing opinion. DRY all the things, one library that solves
all the use cases, bloat bloat bloat. But why?
I'm arguing the very opposite. The point of Open Source, and GitHub, is to
fork.
Use it as is when you can, contribute if it makes sense, but otherwise,
learn from it, fork it, adapt it, rebuild it, make something new or better
or different or niche.
Sure, it will be messy. There will be multitudes of options, some terrible,
some wonderful, and various forces will make the most useful ones float to
the top.
Sounds familiar? Natural selection works like this. Nature forks.
…On 7 September 2017 at 11:51, ignace nyamagana butera < ***@***.***> wrote:
@bpolaszek <https://github.com/bpolaszek> I get your reasons but they do
not invalidate the fact that the class can be made final . If
- Your need is something common
- Solve an actual problem
- Is easy to add without breaking everything
Then the package author/maintainer will add it. Even if the class is
final. The final part will also guarantee less BC breaking.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#54 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AAd4LP4FYehi642QMhWMJBB6W2Ynurhyks5sf7yrgaJpZM4PPZrd>
.
|
It's intriguing that people still think in terms of extending classes so much. I've some thoughts to add:
|
I'm going to use that one :-D |
Hello everyone, At first I did not intend to take part of this topic since I'm not familiar enough with immutability. Your replies are really interesting, because PHP practices are changing (I never heard about immutability before PSR-7 or To my eyes, yes it's a value object, but its role is not only to store a value, it provides factories, functionnalities that compute things and return a result, and given everything we can do with periods in any PHP project, I'm pretty sure that these functionnalities aren't exhaustive. For years we've been taught to "favor composition over inheritance". I fully aknowledge that making the class Thank you, |
You don't use decoration for this, especially since it's a value. An example is Fairly sure you're never gonna go with the first one without acutely regretting it later. |
Of course, but with |
You just add a Since PHP has no type classes, we are forced into inheritance models: if you inherit from |
Honestly, I don't remember why I argued against The only problem with the "composition over inheritance" argument is that if you start to write code that type hints against |
Except that this is a value with a very well defined domain, whereas other implementations will not respect that |
@Ocramius I understand but I agree with @shadowhand. I just don't understand the danger of violating LSP if the common point of inheritance (the class or an interface) is only the getters. But what's the possible danger of allowing this? namespace League\Period;
use DateInterval;
use DatePeriod;
use DateTimeImmutable;
use DateTimeInterface;
// Only read-only methods, no setters
interface PeriodInterface
{
/**
* @return DateTimeImmutable
*/
public function getStartDate(): DateTimeImmutable;
/**
* @return DateTimeImmutable
*/
public function getEndDate(): DateTimeImmutable;
/**
* @return float
*/
public function getTimestampInterval(): float;
/**
* @return DateInterval
*/
public function getDateInterval(): DateInterval;
/**
* @param $interval
* @param int $option
* @return DatePeriod
*/
public function getDatePeriod($interval, int $option = 0): DatePeriod;
// ...
/**
* @param Period $period
* @return bool
*/
public function durationGreaterThan(Period $period): bool;
/**
* @param Period $period
* @return bool
*/
public function durationLessThan(Period $period): bool;
} namespace League\Period;
final class Period implements PeriodInterface {
// ...
} namespace My\App;
use League\Period\PeriodInterface;
final class HolidaySeason implements PeriodInterface {
// ...
} |
As far as I'm concerned, Say you have a Value The same goes for the but that's just me probably 👀 |
Hi @turanct, indeed the semantics of the example was not the better. namespace League\Period;
use DateInterval;
use DatePeriod;
use DateTimeImmutable;
use DateTimeInterface;
interface PeriodInterface
{
// same as above
} namespace League\Period;
final class Period implements PeriodInterface {
// same as above
} namespace My\App;
use DateTimeImmutable;
use League\Period\PeriodInterface;
final class MyPeriod implements PeriodInterface {
// ...
/**
* @return DateTimeImmutable
*/
public function getEndDate(): DateTimeImmutable
{
// I would not really do it this way, but you know what I mean
return $this->endDate->modify('-1 second');
}
// etc.
} My object still represent a period and can be manipulated the same way. This may be a better example? |
@bpolaszek If you do that you've already broken Period contract 👎 having methods with the same name does not validate creating an interface if the meaning behind those methods is already different. |
@nyamsprod I don't agree. The result is the same: a |
@shadowhand no it is not as stated in the documentation website
So what he suggests invalidate his implementation of being a true Period object in my book. just returning DateTimeImmutable object does not satisfy the contract as he deliberatly redefined what a Period is. If his code interacts with mine then we will have an issue because we are not talking about the same thing. |
@Ocramius whether or not other implementations respect the domain is not a forgone conclusion. Providing the interface is saying "this is how Period works" not "this is what Period does". The "doing" is in the implementation, and a good implementation will respect both the contract (interface) and the spirit (documentation, existing implementation) of Period. The OCP says that a class must be open to extension. The LSP says that two implementations should be interchangeable. This can either be done by inheritance (👎🏼) or decoration (👍🏼) but LSP would be violated unless there is an interface that can be shared between the decorator and the decorated. Philosophy aside, if Period becomes |
@nyamsprod sure, fine. The examples given violate some aspect of the "implied contract" of Period. That doesn't mean every implementation will. Throwing out the possibility of using the decorator pattern is, in my opinion, not acceptable. |
You don't need to design to support every pattern: that brings no value at
all.
…On 7 Sep 2017 16:45, "Woody Gilk" ***@***.***> wrote:
@nyamsprod <https://github.com/nyamsprod> sure, fine. The examples given
violate some aspect of the "implied contract" of Period. That doesn't mean
*every* implementation will. Throwing out the possibility of using the
decorator pattern is, in my opinion, not acceptable.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#54 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AAJakEU2a5i1FICdzxtCgNjqxz5USuXTks5sgAGAgaJpZM4PPZrd>
.
|
The point is not about the pattern, it is about OCP and LSP. Setting the class as |
Which is OK 😋
…On 7 Sep 2017 16:59, "Beno!t POLASZEK" ***@***.***> wrote:
You don't need to design to support every pattern: that brings no value at
all.
The point is not about the pattern, it is about OCP and LSP. Setting the
class as final without interface means that there should be only 1
implementation of a Period, that means it won't be extensible nor
interchangeable.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#54 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AAJakJuVvJQqivnsJsjJwZdTvhPEfK_1ks5sgATRgaJpZM4PPZrd>
.
|
Also, we're talking about a Value here. I've never seen values being "decorated", simply because there's no use doing it. Compare it to a Same goes for the |
@turanct the problem is not "modifying" the values (Period setters are immutable), the problem is to override the factories. Simple example: $period = new Period(new DateTimeImmutable('2017-09-01'), new DateTimeImmutable('today -1 second'));
foreach ($period->split('1 day') as $subperiod) {
printf('%s => %s' . PHP_EOL, $subperiod->getStartDate()->format('Y-m-d H:i:s'), $subperiod->getEndDate()->format('Y-m-d H:i:s'));
} Currently outputs:
What if I want this result:
3 options:
Which one should I choose? |
You know that a factory is generally not an instance method, and therefore
doesn't need to be overridden, ever, right?
…On 7 Sep 2017 17:31, "Beno!t POLASZEK" ***@***.***> wrote:
@turanct <https://github.com/turanct> the problem is not "modifying" the
values (Period setters are immutable), the problem is to override the
factories.
Simple example:
$period = new Period(new DateTimeImmutable('2017-09-01'), new DateTimeImmutable('today -1 second'));foreach ($period->split('1 day') as $subperiod) { printf('%s => %s' . PHP_EOL, $subperiod->getStartDate()->format('Y-m-d H:i:s'), $subperiod->getEndDate()->format('Y-m-d H:i:s'));}
Currently outputs:
2017-09-01 00:00:00 => 2017-09-02 00:00:00
2017-09-02 00:00:00 => 2017-09-03 00:00:00
2017-09-03 00:00:00 => 2017-09-04 00:00:00
2017-09-04 00:00:00 => 2017-09-05 00:00:00
2017-09-05 00:00:00 => 2017-09-06 00:00:00
2017-09-06 00:00:00 => 2017-09-06 23:59:59
What if I want this result:
2017-09-01 00:00:00 => 2017-09-01 23:59:59
2017-09-02 00:00:00 => 2017-09-02 23:59:59
2017-09-03 00:00:00 => 2017-09-03 23:59:59
2017-09-04 00:00:00 => 2017-09-04 23:59:59
2017-09-05 00:00:00 => 2017-09-05 23:59:59
2017-09-06 00:00:00 => 2017-09-06 23:59:59
3 options:
- I override a non-final Period in a new child class
- I create a new MyBusinessPeriod that implement PeriodInterface
because final class Period implements PeriodInterface
- I fork the Period library
Which one should I choose?
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#54 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AAJakPpk8yuqLMT4Pyj4QbrDjFl_ajMqks5sgAw9gaJpZM4PPZrd>
.
|
@bpolaszek you are just wrongly using the split method which was not made for that. instead you should use a factory like @Ocramius suggested |
@nyamsprod <https://github.com/nyamsprod> sure, fine. The examples given
violate some aspect of the "implied contract" of Period. That doesn't mean
*every* implementation will. Throwing out the possibility of using the
decorator pattern is, in my opinion, not acceptable.
Every implementation that does not return the same values for the same
inputs is wrong.
Any implementation that returns the exact same values for the same inputs
is correct, but has no reason to exist(*) as it's doing the exact same
thing. Therefore, value objects do not have commonly offer extensibility or
interfaces(***).
(*) Except for non-functional concerns, such as better performance under
some conditions. Ideally, the one implementation that gets accepted gives
the best performance under all conditions.
(**) An "implied contract" is no longer implied if it's documented. Ideally
of course, it's enforceable. PHP lacks features to enforce this kind of
contract, interfaces are very crude.
(***) Typing by value can be a fun use case for interfaces with Value
Objects:
final class Saturday implements Weekday {public function
isWeekend():bool{return true;}) (****)
(****) Before all the DRY lovers start shouting AbstractWeekendDay: trait
Weekend solves this without inheritance, but seriously, in this specific
example, don't. DRY is coupling, and you should only trade off coupling
against reduced maintenance. Value Objects raraely have a maintenance
problem, they tend to be very stable. In other words:
final class Saturday implements Weekday {public function
isWeekend():bool{return true;})
final class Sunday implements Weekday {public function
isWeekend():bool{return true;})
THIS CODE WILL NEVER CHANGE, because if Saturday is not Weekend anymore,
the rebellion will rise! Therefore, no maintenance problem, therefore,
don't DRY.
…On 7 September 2017 at 17:09, turanct ***@***.***> wrote:
Also, we're talking about a Value here. I've never seen values being
"decorated", simply because there's no use doing it.
Compare it to a Coordinate(x, y), which is a value composed out of two
other values. Now, you're saying that you want a Coordinate(2, 3) that
when inspected has in fact x = 2 and y = 4! that's absurd. Why not
instantiate it as Coordinate(2,4) then instead?
Same goes for the Period of 1 day, that is actually 1 second less than
one day. Just instantiate it differently. This is just a composed Value out
of two DateTimeImmutables. There is no way to implement it differently in
a sane way. There is no contract. Contracts talk about behavior, while this
is a value, like 4 is a value. Values can be compared and worked with by
other actors but they don't have any real behavior of their own which can
be extracted to an interface.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#54 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AAd4LLYcL448MLkDbIUg5Ju73YB86df8ks5sgAcwgaJpZM4PPZrd>
.
|
I've never used Period before, but I have implemented similar functionality on my own. In one particular instance I needed a way to generate different types of periods that shared a common interface:
The last one is a little complex - US Federal Elections occur on "the first Tuesday following the first Monday in November". I could be wrong, but it seems like If I were to use league/period for this particular use case, what's the best way to create something that looks and acts like a |
@colinodell a Period consists of 2 DateTimeImmutable objects and that's it. How you calculate those 2 datepoints is out of scope. I could have shipped the Period object without any named constructor if I wanted. It's the developper responsability to correclty compute those endpoints. I don't see how extending the Period object would solve this issue ? |
I could either code against a PeriodInterface or extend Period - both
have their pros and cons, but at least one of these options should be made
available for me.
False dichotomy!
…On 7 September 2017 at 17:48, ignace nyamagana butera < ***@***.***> wrote:
@colinodell <https://github.com/colinodell> a Period consists of 2
DateTimeImmutable objects and that's it. How you calculate those 2
datepoints is out of scope. I could have shipped the Period object without
any named constructor if I wanted. It's the developper responsability to
correclty compute those endpoints. I don't see how extending the Period
object would solve this issue ?
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#54 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AAd4LC2gaXrdb1rH98KKkb74UWzXI49fks5sgBA6gaJpZM4PPZrd>
.
|
I understand that. However, there's also a
That's a fair point. I could create a factory which returns new |
FWIW I don't have a strong opinion on whether |
@nyamsprod @colinodell If a Period only consisted of 2 What makes your class great is that it can read the combination of these 2 objects under the prism of a date range. Apart from that, even if your definition of a period is definitely the most common, @colinodell proved this is not the only one. You can have a different way to read 2 I think we all agree about making the class But what you should state now, is to allow or not people to have a different implementation than yours or not. That doesn't mean you'll need to maintain them. IMHO, if you prefer to prevent composition, everything you've coded is lost for any developer that need something more specific. In this case, I personnally prefer to code my own specific stuff instead of forking and editing something, because this is harder to maintain, and I don't necessarily need all the functionnality of the library I cannot use. And there's nothing more frustrating than reinventing the whole wheel when you have github and composer in front of you. |
@bpolaszek correct me if I'm wrong what you are saying is:
|
That's what I suggest indeed: lock your implementation, allow other implementations. |
I like that approach! |
Different domain should have a different solution. Remember that interfaces can be subject to change, especially if you have a value object. How about maintaining that when you have a use case in a (totally) different domain? What if the interface is not suitable for your use case anymore? Your request is like "give me free software that is suitable for me, and if it is not, provide me a solution that I can make it suitable for me, otherwise you frustrate me." We are talking here of a single class. It's an extreme silly argument. The single and foremost reason to make the object final is to prevent that objects instantiated through this class might become mutable. Value objects are to be immutable for variety of well known reasons. This should be enforced by code, not by hoping that there won't be a developer that extends it and adds a mutable property to the extended class. |
i know comparison is not reason but JodaTime Interval which does essentially the same thing as Period but in Java is marked as Immutable and Final and I don't recall it having any kind of Interface :) . Having said that, @bpolaszek If a PeriodInterface was created it would most likely only feature the current API getters and none of the modifying methods which is if I'm correct the most valuable part of the class in your eyes ? @colinodell yes you are right the |
Hi @nyamsprod, that's what I said, but after all, it makes no sense. The interface is an API contract for manipulating period objects, including modifiers. As a consequence, if I typehint a @frederikbosch I understand that creating an interface for a single class may seem useless. Usually we don't need this because the |
Let's do only things that are widespread and common practise. If the whole world would use that kind of logic, what place would we live in? And since 'not widely used' is subjective, the argument is technically irrelevant. I would advise you to increase your education of value objects, what immutability means, why it makes sense for a value object, and therefore why you would not want to open these classes for mutability. |
I never said that this common practice was a good one, that's the point :) So if the only answer to this is to lock any possible extension / composition / inheritance of any PHP project because of that, let's do this. |
Thanks for the constructive discussion in the next major version |
This is stupid. I don't agree at all with this decision. Now if I need extended MyPeriod which is 100% same only with some more methods I always have to convert between them. $myPeriod = new MyPeriod($period);
$myPeriod = $myPeriod->doSomething(); // this could be 99% same as already existing method, now duplication because it's not possible to reuse.
$period = $myPeriod->toPeriod();
someFunction($period); // someFunction might not be under my control, but expects Period as arguemnt |
@davispuh You should instead use composition to achieve what you need. That is what is being imposed by this final key word. Everything you can do with extension, you can also do by thinking in terms of composition. |
That is what my example shows, it's not as convenient. |
Issue summary
Period
is a value object. as such until version 3.0 thePeriod
class was marked as final. The final keyword was removed as a requested from @shadowhand link to the relevant discussionI am in the process of wrapping up the code for a v4 version which will be PHP7.1+ only. One of the last question I face is should I revert this change and make
Period
final again ?Any input from the community is welcomed @Ocramius , @mathiasverraes, @frankdejonge you are welcome to participate. I should note that I never felt the need to extend the
Period
class. But They are some use cases I may not be taking into account.thanks in advance
The text was updated successfully, but these errors were encountered: