Reputation: 474
Thank you for your time!
Here is a problem which confused me a lot!It's about the Dependency between components in Factory Pattern.
Just like the pic above,we got a Window Factory Model here.
1.Interface: IFatory IWindow,IButton,ITextBox (model definition);
2.Implement: WindowsFactory,MacFatory (to implement the interfaces);
Now,we have changed the old model.So the IWindow has two properties named "CloseButton" and "TitileBox".Problems come out:
1.When and how to implement these two properties?
2.These two properties should have the same style with their container window,which means we can only have WindowsStyle(Window/Button/TextBox) or MacStyle(Window/Button/TextBox).No mixing!
To solve these problems,I had got some trials.Please check the code below.
class MacWindow:IWindow
{
public IButton CloseButton {get;private set;}
public ITextBox TitleBox {get;private set;}
public MacWindow()
{
this.CreateCloseButton();
this.CreateTitleBox();
}
protected virtual void CreateCloseButton()
{
CloseButton = new MacButton();
}
protected virtual void CreateTitleBox()
{
TitleBox = new MacTextBox();
}
}
As you see,this solution is not good.
1.Class MacWindow has to rely on the specific IButton/ITextBox implements (although they are in the same Factory).It feels bad!
2.If someday,we get a new Class derived from the Class MacWindow,we have to override those virtual methods to keep them in the same style.
class MacWindow:IWindow
{
public IFactory ActiveFactory{get;private set;}
public IButton CloseButton {get;private set;}
public ITextBox TitleBox {get;private set;}
public MacWindow(IFactory factoy)
{
this.ActiveFactory=factory;
this.CreateCloseButton();
this.CreateTitleBox();
}
private void CreateCloseButton()
{
CloseButton = ActiveFactory.MakeButton();
}
private void CreateTitleBox()
{
TitleBox = ActiveFactory.MakeTextBox();
}
}
This one looks better,but now quite perfect.
1.Now works are much easier to do ,if we got a new factory derived from MacFactory which is named MacOSXFactory,the only thing we need to do is to override IFactory.MakeButton() and IFactory.MakeTextBox() methods;(er..those methods should be definited as 'public virtual')
2.The dependency on specific implement is no longer existed,but the window class have to inject the dependency on IFactory. It doesn't feel good to have a product known the details about the Factory.
So here is the puzzle! Which solution is better? And is there any other way to solve this problem?(Eager to know!Really thanks!) Could you please give me some advices?
Please allow me to say "Thank you so much!" for your time to read the question!
Upvotes: 3
Views: 250
Reputation: 3961
Well, it took a while for me to understand the intention of the scribbled scenario. You added to many ingredients, so i really had a hard time finding the best hook to answer your question.
First of all, you could easily add Windows components into Mac windows (which was already stated), you created a complete set of duplicated classes which irritated me a bit and the window implementation appears to be a pretty concrete example.
So, the main question is, what are you trying to achieve? You won't get rid of dependencies in the first place and also, dependencies aren't a bad thing. Also, factories should make your life easier, not harder - use them where you need them, not everywhere.
So, your concrete window implementations rely on the concrete button and titlebox implementations. This means in your case, the window implementation should know best about its children. The factory is creating only the window and the window knows which buttons to create. So solution 1 looks good from that point of view. Solution two is actually kind of bad, because the window relies on the factory, which is kind of odd.
If someday,we get a new Class derived from the Class MacWindow,we have to override those virtual methods to keep them in the same style.
Actually, this where my initial question was leading at. You are creating duplicated classes with the same behaviour only to have a certain look (and feel). You could easily reduce the complexity and get a better idea when simplifying the problem and avoid questions about the "when in the distance future". When separating look and feel from behaviour, you'd get rid of many questions regarding the dependencies. The factory would create window instances, perhaps set the styles and you're done.
This would be an extensible approach and easy as well and would also be pretty close to how views are created in modern frameworks (skinning + styling|behaviour|controller).
Upvotes: 1
Reputation: 4643
I agree with Jehof's comment, your solution seems like too abstract. It need some more constraint to ensure that each component does not arrived at different type of window (MS to Mac or vice versa). I use terms MSWindow
here to distinguish.
First, I think it is better to distinguish between MsComponent
and MacComponent
, to prevent wrong dependency injection. 2 interface IMsComponent
and IMacComponent
should be sufficient enough for this. Both should implement IComponentBase
for generics later. Because there is not a way to define a multiple-implementation
type such as {IMsComponent, ITextBox}, you need 3rd interface, that is IMsTextBox : IMsComponent, ITextBox
and IMacTextBox : IMacComponent, ITextBox
.
So the graph becoming like this:
ITextBox IComponentBase
\ |
\ IMsComponent
\ /
IMsTextBox
Then we need a context to handle the components:
IWindowContext<TtextBox, Tbutton> where TtextBox : IComponentBase, ITextBox
where Tbutton : IComponentBase, IButton
{
TtextBox TitleBox {get;set;}
Tbutton CloseButton {get;set;}
}
Please note you can replace the public set with private set and do constructor injection. Then in IWindow, you just need to accept the specific type of IWindowContext
. Such as in MsWindow
class:
public class MsWindow{
public MsWindow(IWindowContext<IMsTextBox, IMsButton> context){
this.CloseButton = context.CloseButton;
this.TitleTextBox = context.TitleTextBox;
}
public ITextBox TitleTextBox {get; private set;}
public IButton CloseButton {get; private set;}
}
For the factory, you can be flexible as to return whether specific windowContext, or a specific window. However I prefer a factory that return a specific windowContext since it is more flexible. Don't forget to make the factory generic also.
public interface IContextFactory<TTextBox, TButton>
where TTextBox : IComponent, ITextBox
where TButton : IComponent, IButton
{
IWindowContext<TTextBox, TButton> Create();
}
A little complex right? But it ensure that the MsWindow
cannot be assigned with MacComponent
. However if can, I don't think you will need this much abstraction. If you want the leaky abstraction, you can go with Context and ContextFactory only, and forget all about the generic.
Upvotes: 1
Reputation: 2579
you should use solution 1!
Class MacWindow has to rely on the specific IButton/ITextBox implements (although they are in the same Factory).
The Factory is needed when you would like to create a generic initialization based on some logic (in your case the OS).
When you create a MacWindow
you already know that all its components will always be Mac's so do you really have to use factory only because you already have it?! of course not
Upvotes: 1
Reputation: 2685
You should instantiate them inside the Factories.
Solution 1 is not good enough because the instantiation business should be implemented in one place and this place is inside the Factory. You already have MakeButton and MakeTextBox implementations in each factory, call them inside the MakeWindow implementation, like as follows;
IWindow MakeWindow() //MacFactory's MakeWindow
{
IWindow window = new MacWindow();
window.TitleBox = this.MakeTextBox();
window.CloseButton = this.MakeButton();
}
With respect to the Single Responsibility principle, creation of TextBoxes and Buttons should be held in Factory because Factory's responsibility is object creation with proper business rules.
Solution 2 is not good enough also because a Window should not know about the Factory. Or a Window does not have any business with its creator, its business is to draw a window on the screen etc. (Loose Coupling)
Upvotes: 2