可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
Is it possible to remove a decorator from an object?
Say I have the following code:
abstract class Item
{
decimal cost();
}
class Coffee : Item
{
decimal cost()
{ // some stuff }
}
abstract class CoffeeDecorator : Item
{
Item decoratedItem;
}
class Mocha : CoffeeDecorator
{
Item decoratedItem;
public Mocha(Coffee coffee)
{
decoratedItem = coffee;
}
}
public void Main(string[] args)
{
Item coffeeDrink = new Mocha(new Coffee());
}
Is there a way to remove the "new Mocha()" from my new "coffee" object?
EDIT: Clarification - I want to be able to remove just ONE decorator, not all of them. So if I had a Mocha decorator AND a Sugar decorator on the Coffee object, I want to know if I can remove just the "Mocha" decorator.
回答1:
First, this assignment is not legal:
Coffee coffee = new Mocha(new Coffee());
A Mocha
is not a Coffee
nor is there an implicit cast from a Mocha
to a Coffee
. To "remove" the decorator, you need to provide either a method or a cast to do so. So you could add an undecorate method to Mocha
:
public Coffee Undecorate() {
return (Coffee)decoratedItem;
}
Then you could say
Coffee coffee = new Mocha(new Coffee()).Undecorate();
Alternatively, you could provide an implicit cast operator in the Mocha
class:
public static implicit operator Coffee(Mocha m) {
return (Coffee)m.decoratedItem;
}
Then your line
Coffee coffee = new Mocha(new Coffee());
would be legal.
Now, your question suggests a potential misunderstanding of the design pattern (and, in fact, your implementation suggests one too). What you're trying to do is very smelly. The right way to go about using the decorator pattern is like so. Note that CoffeeDecorator
derives from Coffee
!
abstract class Item { public abstract decimal Cost(); }
class Coffee : Item { public override decimal Cost() { return 1.99m; } }
abstract class CoffeeDecorator : Coffee {
protected Coffee _coffee;
public CoffeeDecorator(Coffee coffee) { this._coffee = coffee; }
}
class Mocha : CoffeeDecorator {
public Mocha(Coffee coffee) : base(coffee) { }
public override decimal Cost() { return _coffee.Cost() + 2.79m; }
}
class CoffeeWithSugar : CoffeeDecorator {
public CoffeeWithSugar(Coffee coffee) : base(coffee) { }
public override decimal Cost() { return _coffee.Cost() + 0.50m; }
}
Then you can say:
Coffee coffee = new Mocha(new CoffeeWithSugar(new Coffee()));
Console.WriteLine(coffee.Cost()); // output: 5.28
Given this, what do you need to undecorate it for?
回答2:
To build on and clarify what John K. said, Decorator Pattern can be thought of as a Linked List-at which point adding a setter is only natural.
To remove a layer, simply point its parent-link's reference at its child-link; or, in Decorator Pattern terms, to remove decorator foo, point foo's decorator's decorated-object reference to foo's decorated-object.
回答3:
Using a reference to the child and parent Item can be overwhelming sometime.
Another way to go is to implement an abstract decorator class where a boolean state will tell you if the decorator has to be considered "on" or "off". Using an abstract class will allow you to put all the logic for the remove decorator in one place, then all the concrete decorators can be build on top of that without worrying about the remove.
You will have have a remove decorator method that set to true this variable if this is the decorator that you want to remove, doesn't matter the position of the decorator in the chain of decorations:
public void RemoveDecorator(DECORATOR_CODES decCode)
{
if (this.Code == decCode)
{
bDecoratorRemoved = true;
}
else
this.ParentBevarage.RemoveDecorator(decCode);
}
public float Cost()
{
if (!bDecoratorRemoved)
return this.ParentBevarage.Cost() + this.Price;
else
return this.ParentBevarage.Cost();
}
You are not really removing the decorator but you are neutralizing its effect, it's not the more efficient in term of memory, but definitely will allow you to remove any decorator, how many you want and with only few lines of code. If the Item object are not too many and they live short it can be worthy.
回答4:
I would undecorate by calling a method to replace the current wrapped object, I mean, if I have the decorators A
,B
,C
,D
,E
, it means that E
wraps D
which wraps C
which wraps B
which wraps A
. So, by calling a method and replacing the wrapped object, we can remove a desired decorator, e.g. if we want to remove the decorator C:
factory.RemoveDecorator(decoratedObj, replaceDecorator)
So, the decorated object will wrap the object in the second parameter. depending on the decorator we need to remove we will call the method removedecorator
several times. If we want to call it just once we can write a method in our factory to find which object will be removed.
回答5:
If you code it with more flexiblity you can
To remove one decorator, unpeel them all and reassemble without the one you want to leave out. To unpeel you'll need to be able to reference each one. Add a property that expresses the wrapped decoration and the innermost decoration will express null.
interface IDecoratedExpressing {
IDecoratedExpressing InnerDecorated {get;}
}
then
// NOTE: implement IDecoratedExpressing for all decorations to provide a handle.
// Example of first:
class Mocha : CoffeeDecorator, IDecoratedExpressing
{
Item decoratedItem;
// express inner
public IDecoratedExpressing InnerDecorated {
get {return decoratedItem;}
}
public Mocha(Coffee coffee)
{
decoratedItem = coffee;
}
}
Maybe make the InnerDecorated
property settable too, so you can put them back together in a different way (or to leave one or more out). This means you can manipulate a decoration through a setter property instead of just at construction time. Allows flexibility. Unsure how kosher this is. Just thinking on the fly.