Native support for the Decorator pattern #862
Replies: 17 comments
-
There would need to be some way to supply and/or replace the decorated item. Being able to do that is one of the benefits over inheritance. |
Beta Was this translation helpful? Give feedback.
-
@bondsbw True. The default constructor of a decorator would require passing in the decorated object. |
Beta Was this translation helpful? Give feedback.
-
@nehmebilal I think you have a typo and the interface should look like this:
|
Beta Was this translation helpful? Give feedback.
-
@eyalsk Corrected, thanks :) |
Beta Was this translation helpful? Give feedback.
-
@nehmebilal I'd go with something like this:
|
Beta Was this translation helpful? Give feedback.
-
@eyalsk That would be a bigger language alteration. I'd rather focus on a minimal amount of changes to make this feature possible. |
Beta Was this translation helpful? Give feedback.
-
My opinion is that decorators are a great use case for source code generators since they are primarily boilerplate. For example, the following: [Decorator(typeof(ITimeService))]
public partial class TimeServiceRetryDecorator {
private readonly RetryPolicy retryPolicy;
public TimeServiceRetryDecorator(ITimeService subject, RetryPolicy retryPolicy) : this(subject)
{
this.retryPolicy = retryPolicy;
}
public Task<DateTime> GetTime()
{
return retryPolicy.ExecuteAsync(() => subject.GetTime());
}
} could generate the following source: public partial class TimeServiceRetryDecorator : ITimeService {
private readonly ITimeService subject;
public TimeServiceRetryDecorator(ITimeService subject
{
this.subject = subject;
}
public TimeZone CurrentTimeZone
{
get { return this.subject.CurrentTimeZone; }
set { this.subject.CurrentTimeZone = value; }
}
} |
Beta Was this translation helpful? Give feedback.
-
@nehmebilal I believe that the syntax can be more concise and better suitable for the task. You suggested to add the It really reminds me traits in a way and I think that having public by default make sense, the names of the variables can be inferred from the constructor and interfaces are implemented using the implementation instances that are passed to the constructor and matched by their type. I didn't like the fact that some of the parameters are implicit, it may make this harder for documentation and people may want to be explicit about the name for clarity and it might also confuse people more than it would help them. Finally this would generate the same thing as you posed in your post:
@HaloFour But then can't we say the same about records and whatnot? and then the big question is when are we going to get it? I think it's really the far future. :) |
Beta Was this translation helpful? Give feedback.
-
To an extent, sure. A lot of boilerplate-heavy code could be handled by generators. It would be more expedient to implement them this way also since you're not waiting on the C# team, and you wouldn't be held to a single opinion as to how that source should work.
Well that's the real question. But as long as they're on the table and they're "championed" I think that they are the better route. As a feature they are more likely to be implemented in the short term compared to these specific use cases which would likely pile up until it can be demonstrated that source generators aren't feasible. |
Beta Was this translation helpful? Give feedback.
-
@eyalsk That makes sense. Sorry, my concern was about the constructor provided without an implementation, which is usually not allowed in classes. The reason I used @HaloFour Sure, code generation is better than nothing but not as good as native support 😃 . In fact, many developers avoid code generation because it often leads to refactoring issues and have other side effects (e.g. IDE tools such as navigation, find usages, etc, will bring up the generated code, which can be annoying). Another possible approach is to have the IDE generate the default implementation that delegates all the calls to the decorated object and then the developer can change the methods that need to be decorated. I actually noticed that VS code is able to do that when I was writing the example above! The downside is that the generated boiler plate will be there for everyone to see and maintain.. I believe decorators should be used very often in OO applications, more often than inheritance, which is why native support seems relevant to me. |
Beta Was this translation helpful? Give feedback.
-
Yes and it
|
Beta Was this translation helpful? Give feedback.
-
Duplicate of #234? |
Beta Was this translation helpful? Give feedback.
-
The approach in #234 would be one way of solving the problem but not exactly a duplicate. This proposal is focused on embracing the decorator pattern as part of the language. |
Beta Was this translation helpful? Give feedback.
-
@jnm2 I thought about it but the other proposal is in my opinion neither detailed nor thorough. |
Beta Was this translation helpful? Give feedback.
-
I'm really curious for what it will look like when using the decorators, if it will still be I think the word Other than that, I'm very interested in it. |
Beta Was this translation helpful? Give feedback.
-
Source generators have landed so I believe this can now be solved without any special-case language changes |
Beta Was this translation helpful? Give feedback.
-
FYI Kotlin now supports this: https://kotlinlang.org/docs/delegation.html#overriding-a-member-of-an-interface-implemented-by-delegation, I hope this motivates the dotnet community :) |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
Similarly to the
extends
keyword that is being added for extending interfaces, I propose adding adecorates
keyword for decorating interfaces, which would encourage C# developers to choose composition over inheritance and write code that is more reusable and testable.I actually published a full article about this last year (Is Inheritance Dead?), which was very well received. The article discusses in details the benefits of the decorator pattern and how native support would encourage developers to write better code.
Let me illustrate with an example. Consider the following
ITimeService
interface.The default implementation (
DefaultTimeService
) simply makes a request to the time service and returns the current time. If the request fails, an exception is thrown. We'd like to add retries to the default implementation to handle transient failures. One option is to add retries directly in theDefaultTimeService
implementation but that has several drawbacks.DefaultTimeService
.ITimeService
.Another approach is to use inheritance but as discussed in the Is Inheritance Dead?, inheritance can lead to an exploding class hierarchy and its use is limited because C# does not support multiple inheritance (it also has the same testability and reusability issues above).
The better approach is to use a decorator as show below:
With native support for the decorator pattern, the implementation would look as follows:
By default, all methods that are not implemented will delegate the call to the
subject
(which is similar tobase
andthis
). For interfaces that have many methods and properties, this would result in significantly less code.Another benefits of the decorator is that multiple decorators can be stacked on top of each other, each adding new functionality (see this article for more info).
I am not a compiler expert but I suspect that language level understanding of the decorator pattern would also allow the compiler (or JIT) to inline decorator calls.
Finally, I think that this feature would encourage developers to add behaviour using decorators instead of modifying existing classes, which aligns well with OO principles.
Thoughts?
Beta Was this translation helpful? Give feedback.
All reactions