Suppose I am logging some data with a 'Job' class. (A list of business objects, with various properties, for what it's worth.)
I wish to be able to print this data, so I am wondering if there is a more preferred design for doing this. I have two ideas at the moment - calling a Print() method on the Job itself, or passing a Job instance 开发者_Go百科to some sort of print controller class, e.g:
job.Print();
or
PrintWidget pw = new PrintWidget(job);
pw.Print();
At the moment, I cannot envisage printing anything other than the data from this Job class. However, who knows what the future holds. With this in mind, would it be better to have individual Print() methods on any classes I would like to print, or one Print controller class that can handle different types of things to print?
How would you go about designing this? Thanks in advance for any answers.
Your problem fits squarely into the single responsibility principle (SRP), one of the SOLID principles.
The principle essentially states that a well designed component should only ever be responsible for a single task. Another way of looking at it is that the component should only ever have one reason to change. This makes sure that when you need to change something, you know all the functionality will be in a single place and wont be mixed with other functionality.
In your case, a Job
presumably has some kind of functionality other than printing, perhaps some responsibilities to represent a "job" in your domain model. If it does, then you don't want to add printing functionality here. Instead create your print widget and put all the logic for printing there.
If your printing has to change, you wont have to touch your Job
object, because its responsibilities haven't changed. Likewise if the concept of a "job" changes, only the Job
class is modified and the printing remains unaffected (unless you now have something extra to print).
However...
If the only purpose of the Job
class is to represent some information for printing, then it most definitely should contain a PrintTo(Printer)
method, where Printer
would be responsible for talking to a physical printer. In this case the responsibility has shifted to Job
being only involved in printing and it's appropriate it should control how it is printed.
SRP can be a difficult pattern to see in your designs, but there is a simple way to be sure:
If you can sum up the functionality of your class without using the word "and", the class has a single responsiblity.
So, Job
is either responsible for "representing a job" or "printing job information", but not both.
Is the knowledge of printing a key part of your Job
class? If not, it probably shouldn't be in that class (separation of concerns etc). This applies doubly if printing can be different in different contexts. I would probably do something more like:
PrintWidget pw = new JobPrintWidget(); // perhaps via abstract-factory
pw.Print(job);
As for what PrintWidget
is... if you need to support other types in the future, I'm guessing you might either have a concrete implementation of an abstract base class (as shown above, where Print
is an abstract method), or alternative an interface.
As to me the job feels more like input, but if the widget only has to print 1 job, so be it.
Another possibility (in C# 3) is an extension method:
static class PrintUtil {
public static void Print(this Job job) {...}
}
which allows you to use job.Print()
, but without putting the code into Job
.
One option is to use Extension Methods, which was introduced in C# 3.0. Extension methods allow you to extend a class without deriving from it, which allows greater flexibility. Using extension methods, you could add a Print method to the Job class and any other class without altering the original class. E.g.:
static class PrintExtensions
{
public static void Print (this Job job)
{
// TODO: print Job
}
public static void Print (this SomeOtherClass c)
{
// TODO: print SomeOtherClass
}
}
Once you define an extension method, you use it as if it were defined on the class itself, e.g.:
Job job = GetJob();
job.Print(); // call PrintExtensions.Print(this Job job)
Personally, I would put the Print method in the Job class. If you create a separate PrintWidget class, it will have to know all about the Job class. Anyway, if it turns out that you do need or want a PrintWidget in future, you can always create it then and refactor the Job class's Print method over to it.
精彩评论