Reputation: 87
So I've created something like this:
interface IStudent
{
string DisplayInformation();
}
public class Student : IStudent
{
public string Name { get; set; }
public string Grade { get; set; }
public int Age { get; set; }
public virtual string DisplayInformation()
{
return $"{Name} - {Age} years old is in {Grade} grade";
}
}
public class StudentDecorator : Student
{
private Student _student;
public StudentDecorator(Student student)
{
_student = student;
}
public override string DisplayInformation()
{
return _student.DisplayInformation();
}
}
public class ScienceStudentDecorator : StudentDecorator
{
public string Labs { get; set; }
public ScienceStudentDecorator(Student student) : base(student)
{
}
public override string DisplayInformation()
{
var info = base.DisplayInformation();
return $"{info}. Labse are {Labs}";
}
}
And I am decorating the Student like that:
var student = new Student
{
Age = 15,
Grade = "Fourth",
Name = "John"
};
var scienceStudent = new ScienceStudentDecorator(student)
{
Labs = "Biology, History, Physics"
};
Console.WriteLine(scienceStudent.DisplayInformation());
Console.Read();
What makes me wonder is if I change the ScienceStudentDecorator to inherit and hold the Student it works exactly the same. I am still decorating the Student. I just skip the ceremony with Student Decorator. And my question is have I misunderstood the concept?
Changed version:
public class ScienceStudentDecorator : Student
{
private Student _student;
public string Labs { get; set; }
public ScienceStudentDecorator(Student student)
{
_student = student;
}
public override string DisplayInformation()
{
var info = _student.DisplayInformation();
return $"{info}. Labse are {Labs}";
}
}
The creation of both Student and ScienceDecorator is exactly the same.
Upvotes: 1
Views: 197
Reputation: 17658
First of all; you spotted the alternative, which makes me say you understood the concept.
The part you are missing is the part where inheritance is bad. Well, that is; if you want to copy the interface but not the behavior.
The key point of the decorator pattern is that it's an
alternative to inheritance
, with the same ability to alter and extend behavior at run time and without being tied to some base class with a specific version or other dependencies.
And that is why, you should make your ScienceStudentDecorator
based on the IStudent
interface only and decorate with the IStudent
instead of Student
(although .net's Stream
classes tend to break this rule. [and I also tend to create an abstract NullStudent
just to keep decorating simple])
Another thing which I want to point out, the beneath decorator
object isn't necessary. The following code would suffice as well, note; the non-behavioral-inheritance:
public interface IStudent //this would rather be called an IInformationDisplayer
{
string DisplayInformation();
}
public class Student : IStudent
{
public string Name, Grade, Age, etc... { get; set; }
private IStudent _student = null;
public Student() { }
public Student(IStudent student) { _student = student; }
public string DisplayInformation()
{
return $"{_student?.DisplayInformation()}" +
$"{Name} - {Age} years old is in {Grade} grade";
}
}
public class ScienceStudent : IStudent //it's still a decorator
{
public string Labs { get; set; }
private IStudent _student;
public ScienceStudentDecorator(IStudent student)
{
_student = student;
}
public string DisplayInformation()
{
var info = _student?.DisplayInformation();
return $"{info}. Labse are {Labs}";
}
}
update To make it even more clear, let us examine the following case:
Imagine you have a IDrawable
type:
public interface IDrawable
{
//maybe with some additional properties as dimensions and position
void Draw();
}
Next we have a rectangle which we would like to draw (note this also could be a house or a wall, a FileStream or a MemoryStream, you think it, you'll name it.)
But, lets keep it with a rectangle:
public class Rectangle : IDrawable
{
private IDrawable _drawable;
public class Rectangle(IDrawable drawable)
{
_drawable = drawable; //just to make it uniform
}
private InernalDraw() { ... }
public void Draw()
{
//do the drawing magic
InernalDraw(); //some drawing code
//and do something with the decorated item
_drawable?.Draw();
}
}
Nothing special yet. But let us assume we want to draw a border around the rectangle.
Let us create a border drawable:
public class Border : IDrawable
{
private IDrawable _drawable;
public class Border(IDrawable drawable)
{
_drawable = drawable;
}
private InternalDrawWithSomeSpecialLogicAndStuff() { ... }
public void Draw()
{
//draw the decorated item:
_drawable?.Draw();
//and work out some magic to draw a border around it,
//note that properties as dimensions would be helpful.
InternalDrawWithSomeSpecialLogicAndStuff();
}
}
Now, let us consider the following code:
public static void Main()
{
IDrawable borderlessRectangle = new Rectangle(null);
IDrawable borderedRectangle = new Border(borderlessRectangle);
borderlessRectangle.Draw();//gives a rectangle without a border
borderedRectangle.Draw();//gives a rectangle with a border
}
Upvotes: 2
Reputation: 460
The concept of decorator pattern is to get alternative from inheritance and loose coupling. You got the basic concept of decorator. My suggestion is to put also one level of abstraction for base decorator which acts as a base giving us place which will default any decorator behaviour required by interface, while concrete decorator implementations will just focus on decoration.
public interface IStudent
{
string Name {get;}
string DisplayInformation();
}
public class Student : IStudent
{
public string Name { get; set; }
public string DisplayInformation()
{
return $"{Name} - {Age} years old is in {Grade} grade";
}
}
/// Base for decorators which stores our base in constructor
/// Gives default behaviour to all props and methods required by Interface
public abstract class StudentDecorator : IStudent
{
protected IStudent BaseStudent {get;}
protected StudentDecorator(IStudent student)
{
BaseStudent = student;
}
public virtual string Name { get { return BaseStudent.Name;} }
public virtual string DisplayInformation()
{
return BaseStudent.DisplayInformation();
}
/// Concrete decorator, we don't need to override Name property
public class ScienceStudentDecorator : StudentDecorator
{
public string Labs { get; set; }
public ScienceStudentDecorator(IStudent student) : base (student)
{
///Behaviour here done by abstract constructor
}
public override string DisplayInformation()
{
var info = BaseStudent?.DisplayInformation();
return $"{info}. Labse are {Labs}";
}
}
Upvotes: 2