Reputation: 24789
I hava a base type (A) which has two derivatives (B and C). The base type is not abstract. So, I have three objects.
The only difference between B and C is that they both have one extra different property:
Now I have conditions like this in my code:
if(myObject is B)
myDatabindB.DataSource = ((B)myReport).Foo);
else if(myObject is C)
myDatabindC.DataSource = ((C)myReport).Bar);
and in another method:
pnlSomePanel.Visible = myObject is B;
pnlSomeOtherPanel.Visible = myObject is C;
But you can imagine that when there's a new type I have to update all my if-else statements. This violates a lot of OO principles.
But the problem is that I can't think of a nice and clean solution to solve this issue. Do you have a suggestion / idea to solve this problem?
EDIT: If it matters, I am using the MVP pattern.
Upvotes: 1
Views: 125
Reputation: 872
First, it's good that you asked this with only three items--it makes fixing problems much faster :). Your code's very generic, so I can only offer generic solutions.
The big goal here is to increase the encapsulation of classes A, B, and C--to make sure that anything relevant to A, B, or C is stored within those classes and not moved to, say, if-statements elsewhere.
We can move the logic for figuring out what the correct datasource is from the Controller (which is doing your binding) to your report. This method's name should be descriptive, like GetReportSubjectLine().
class A{
<snip>
public virtual SomeDataType getDataSourceForViewType(){
throw new NotImplementedException()
}
}
class B{
<snip>
public override SomeDataType getDataSourceForViewType(){
return this.Foo;
}
}
class C{
public override SomeDataType getDataSourceForViewType(){
return this.Bar;
}
}
This code will be reusable if you ever want to make different UI's that still need this type of information from your report to generate whatever graphical view you're generating.
There's no good way around the second problem you presented. We could always move the panel visibility into the reports too, but that increases coupling--how much one class is tied to another--way too much. Your reports should not be tied to a specific view.
The best solution is to add another layer of indirection--in this case, an intermediary class to handle the logic of what panels to make visible when. This way your controller doesn't have to bear the responsibility of managing panel visibilities itself.
public class PanelVisibilityManager{
ICollection<Panel> ManagedPanels {get; set;}
//
public IDictionary<System.Type, ICollection<Panel>> Switchboard {get; set;}
public void TogglePanelsFor(System.Type item){
foreach(var panel in ManagedPanels){
panel.Visible=false;
}
foreach(var panel in Switchboard[item]){
panel.Visible=true;
}
}
Hope this helps!
Upvotes: 2
Reputation: 45101
How about a Dictionary<Type, Action>
?
Then you could do something like this:
var myActors = new Dictionary<Type, Action<BaseClass>>();
myActors.Add(typeof(classA), DoSomethingWithA);
myActors.Add(typeof(classB), DoSomethingWithB);
...
Action actor;
if(myActors.TryGetValue(specialRetrievedOnlyAsBase.GetType(), actor))
{
ResetEverything();
actor(specialRetrievedOnlyAsBase);
}
else
{
// ToDo: What should happen if this type is not supported?
}
...
private void DoSomethingWithA(BaseClass)
{
var classAObject = (ClassA)BaseClass;
// ToDo: What should happen if classA arrives?
}
private void DoSomethingWithA(BaseClass)
{
var classAObject = (ClassB)BaseClass;
// ToDo: What should happen if classB arrives?
}
Upvotes: 0
Reputation: 62246
One the ways to avoid that type of code is move decisional responability into object itself. For example:
Define somewher collection of A.
List<A> objects = new List<A>{new B(), new C()}
Instead of having if/else
use foreach
over collection and calll on every object a virtual method defined in A and overriden in childs, like
virtual bool ThisIsMe(A objectToCheck){}
B and C override this method by checking if objectToCheck
is their type and return true
or false
in regard of it.
EDIT
Example:
public class A
{
public virtual bool ThisIsMe(A objectToCheck){}
public virtual object GetData{}
}
public class B : A
{
public override bool ThisIsMe(A objectToCheck)
{
return objectToCheck is B;
}
public override object GetData()
{
return this.Foo;
}
}
public class C : A
{
public override bool ThisIsMe(A objectToCheck)
{
return objectToCheck is B;
}
public override object GetData()
{
return this.Bar;
}
}
Now instead of that if/else
, something like this:
foreach(A objA in objects)
{
if(objA.ThisIsMe(myObject))
{
myDatabindB.DataSource = objA.GetData();
break;
}
}
May be also substitude this with some fancy LINQ
instruction.
Hope this helps.
Upvotes: 0
Reputation: 761
Strategy Pattern fits here pretty well for the first case
For the second case if you have one to one mapping of your panels you can end up with a static readonly Dictionary<Type, Panel> panels if there are many of types. There is a tab control in WinForms to show some particular tab as well
Upvotes: 0