-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
proposal: Prefer extending this class over implementing it
backed by annotation on the class
#58709
Comments
<rule_name>
Prefer extending this class over implementing it
Assuming we don't add language support for this fairly soon, we'd probably want to implement this by creating an annotation that could be added to class declarations indicating that they shouldn't be implemented. @munificent I believe the language team has also had discussions about visibility / usability modifiers. It would be good to use that work to inform any set of annotations we might add. |
See also: #46331 |
The main class capabilities that we've discussed are (1) creating an instance (currently disabled using Some constraints can be (partially) enforced by accident: For instance, if a class In any case, the purpose of preventing So it's probably a good idea to abstain from naming one specific purpose, and just refer to the actual constraint, e.g., using The language team has had discussions about using negative mechanisms (like I don't think it is crucial that any metadata based mechanisms like the one proposed here is exactly like an upcoming language mechanism. Of course, when |
I recommend against this proposal. I think the recommendation to extend an abstract class, rather than implement an interface, simultaneously avoids the root problem, and encourages harmful behavior. First, in the average case, I believe that the root problem that this proposal aims to solve is not the decision to implement an interface, but rather the framework's decision to add more behaviors to an existing interface. Adding behaviors to existing interfaces is a clear violation of the open-closed principle. While it may not always be possible to conform to the open-closed principle, I would strongly discourage the official Flutter/Dart orgs from publicly incentivizing developers to violate this principle in perpetuity. Second, if one asks "why" an interface keeps expanding, it's likely that one will discover repeated violations of the single responsibility principle. The interface is likely adding responsibilities that belong within different roles. Furthermore, as the API expands along multiple dimensions, it's likely that many of these methods are left empty. After all, this proposal is based specifically on the idea that it's OK to leave methods stubbed, doing nothing. In practice, this means that many apps have implementations that don't do the expected things when those objects are passed to various methods. For example, imagine a new stub called We're now looking at likely violations of the "SOL" in "SOLID" principles. Beyond the violations of software engineering principles, there's also a very concrete and pragmatic problem. You can only extend one class. This fact is probably the greatest area of frustration for all developers who use classical inheritance. Fortunately, Dart offers great alternatives with interfaces and mixins. But this proposal aims to eliminate half of those options. It's interesting that Asking developers to become less fragile to an unstable underlying interface does not help solve the problem of an unstable interface. In fact, I believe that this lint helps reduce a pain that developers should feel. It should be painful to work with monolithic, mutating interfaces. That pain should result in a redesign of those interfaces to be compositional, with explicit design towards future behavior expansion. |
I just wanted to add here that an added benefit with being able to force a client to extend your class rather than implement it is that you are also able to maintain some internal assertions. For instance, defining a I think the ability to have this kind of added safety is the main reason I would like to see some form of this lint or preferably annotation approach as described above. I think being able to specify the intent of a class with more analyser support seems quite invaluable. I'm thinking here of for instance classes like |
What you want, if you need to enforce a positive duration, is a factory method, or some other instance creation tool. Not a subclass. Subclassing is a WAY overused programming tool. It's also one of the least understood programming tools. Please do not make it easier to propagate bad decisions around subclassing. If a class wants clients to conform to a special contract, put those details in the Dart Docs. Preconditions are a common documentation detail. If a developer is going to extend your class, then I think it's reasonable to expect that developer to read the docs. And, again, subclassing is hugely restrictive and easy to get wrong, I don't think many developers should be operating under the pretext that clients need to subclass their creations. Let's teach developers how to build more effective APIs, rather than incentivize common structural mistakes. |
Prefer extending this class over implementing it
Prefer extending this class over implementing it
backed by annotation on the class
@HansMuller, as of Dart 3.0, the support for class modifiers includes I think the If you do not agree then it would be great if you could give some reasons why |
prefer_extends_over_implements
Description
Implementing this class is unsafe because public methods may be added to the class in the future. Extending the class is safe.
Details
Methods can't be safely added to an abstract class or a mixin that has been implemented rather than extended. Abstract classes that exist over long periods of time and many versions, like Flutter's TextInputClient may cause problems for developers if they are implemented rather than extended.
An annotation like @preferExtends would be needed to mark abstract classes like this.
Kind
Helps preserve forwards compatibility.
Good Examples
Years later...
MyFrobComm still compiles and MyFrobComm.doSomething() callers still DTRT. Note of course that considerable thought is required by the abstract class developer to ensure that existing code will not break after doSomethingElse() is added.
Bad Examples
Now MyFrobComm fails to compile when FrobComm.doSomethingElse (with a stub implementation) is added years later.
Discussion
Flutter has about 250 public abstract classes and it would likely be helpful to discourage developers from using
implements
with at least some of them.The text was updated successfully, but these errors were encountered: