views:

271

answers:

7

Does this give any code smell or violate SOLID principles?

public string Summarize()
{
IList<IDisplayable> displayableItems = getAllDisplayableItems();
StringBuilder summary = new StringBuilder();

foreach(IDisplayable item in displayableItems)
{
    if(item is Human)
        summary.Append("The person is " + item.GetInfo());

    else if(item is Animal) 
        summary.Append("The animal is " + item.GetInfo());

    else if(item is Building) 
        summary.Append("The building is " + item.GetInfo());

    else if(item is Machine) 
        summary.Append("The machine is " + item.GetInfo());
}

return summary.ToString();
}

As you can see, my Summarize() is tied to implementation classes such as Human, Animal, etc.

Does it smell? Am I violating LSP? Any other SOLID principles?

+17  A: 

I smell a little something...

If your classes all implement IDisplayable, they should each implement their own logic to display themselves. That way your loop would change to something much cleaner:

public interface IDisplayable
{
    void Display();
    string GetInfo();
}

public class Human : IDisplayable
{
    public void Display() { return String.Format("The person is {0}", 
        GetInfo());

    // Rest of Implementation
}

public class Animal : IDisplayable
{
    public void Display() { return String.Format("The animal is {0}", 
        GetInfo());

    // Rest of Implementation
}

public class Building : IDisplayable
{
    public void Display() { return String.Format("The building is {0}", 
        GetInfo());

    // Rest of Implementation
}

public class Machine : IDisplayable
{
    public void Display() { return String.Format("The machine is {0}", 
        GetInfo());

    // Rest of Implementation
}

Then you can change your loop to something much cleaner (and allow the classes to implement their own display logic):

foreach(IDisplayable item in displayableItems)
    summary.Append(item.Display());
Justin Niessner
Thanks, but what if I have to have some kind of logic within Summarize() method to see what kind of IDisplayable items are in the list? I guess my question is whether it is a bad practice to tie Summarize() with the concrete implementation classes.
SP
@SP - Yes. If you're tying the behavior of Summarize() to concrete implementations, you're really negating the benefits of treating the objects polymorphically via the IDisplayable interface. If behavior needs to change in Summarize, it should do so through the members of the IDisplayable interface.
Justin Niessner
@SP, perhaps explain more what you want to do. For example, IDisplayable might have 1000 implementing types, but you only care about 5 of them.
Anthony Pegram
Perhaps my example was simplistic to reveal my intent. My "real" Summarize() is doing MORE than just displaying, it has logic to see what TYPES such as Human, Animal, etc there are. My Summarize() has specific logic for when there is Human only, but if there are Human and Animal, it does something else. So, as you can see, there is logic in it.
SP
@SP - If that's the case you should update your question so that we have a better idea of what you're doing.
Justin Niessner
@SP From what I think you're saying, you could store that logic in the classes and just do what @justin shows above. Unless you can provide more detailed code, Justin's answer is *the answer* (Or my answer, which is essentially the same answer, just worded differently).
George Stocker
@SP: would this be a similar situation to what you're describing? You want to draw a list of items. If the list contains humans only, the want the list to have a blue border. If the list contains animals only, you want the list to have red border. If the list contains both, it should have a purple border. If so, this requires knowing the types of the contents of the list in aggregate, and you cannot do that polymorphically, in which case I believe my answer is the best choice: http://stackoverflow.com/questions/3637377/does-this-method-violate-solid-or-has-code-smell/3637573#3637573
rmeador
+5  A: 

seems like IDisplayable should have a method for the display name so you can reduce that method to something like

summary.Append("The " + item.displayName() + " is " + item.getInfo());
Joey
Welcome to StackOverflow, enjoy your stay and remember to tip your waitress. Also try to use proper code formatting when at all possible!
Anthony Pegram
+1  A: 

How about:

    summary.Append("The " + item.getType() + " is " + item.GetInfo()); 
froadie
hmmm... why the downvote?
froadie
+3  A: 

Yes.

Why not have each class implement a method from IDisplayable that shows their type:

interface IDisplayable
{
    void GetInfo();
    public string Info;
}
class Human : IDisplayable
{
   public string Info
   { 
    get 
    { 
        return "";//your info here
    }
    set;
   }

   public void GetInfo()
   {
       Console.WriteLine("The person is " + Info)
   }
}

Then just call your method as follows:

foreach(IDisplayable item in displayableItems)
{
    Console.WriteLine(item.GetInfo());
}
George Stocker
+1  A: 

At a minimum it violates LSP and Open-Closed Principle.

The solution is to have a Description property to the IDisplayable interface, so that summarize can just call

summary.Append(string.Format("The {0} is {1}", item.Description, item.GetInfo()));

This could also be solved via reflection, since you're only getting the name of the class.

The better solution is to return something like an IDisplayableInfo from the GetInfo() method. This would be an extensibility point to help preserve OCP.

Jay
Could you briefly explain why it violates LSP? I understand it violates OCP.What if I have additional logic in Summarize() to check what types of implementation classes there are?
SP
@SP There may be a subtle distinction to be made here. A common way the principle is stated is `Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it.` What does "without knowing it" mean? In one sense your method does work with any implementation of `IDisplayable` -- it will compile and won't throw an exception. In another sense, it doesn't work because it has to "know" the implementation in order to do something meaningful with the `IDisplayable`.
Jay
A: 

If you can't modify IDisplayable or the class implementations and you're using .NET 3.5 or later you can use Extension methods. But that's not that much better

Conrad Frix
+1  A: 

Given the comment on this answer from the OP, I think the best approach would be to create a custom container class to replace IList<IDisplayable> displayableItems which has methods like containsHumans() and containsAnimals() so you can encapsulate the icky non-polymorphic code in one place and keep the logic in your Summarize() function clean.

class MyCollection : List<IDisplayable>
{
    public bool containsHumans()
    {
        foreach (IDisplayable item in this)
        {
            if (item is Human)
                return true;
        }

        return false;
    }

    // likewise for containsAnimals(), etc
}

public string Summarize()
{
    MyCollection displayableItems = getAllDisplayableItems();
    StringBuilder summary = new StringBuilder();

    if (displayableItems.containsHumans() && !displayableItems.containsAnimals())
    {
        // do human-only logic here
    }
    else if (!displayableItems.containsHumans() && displayableItems.containsAnimals())
    {
        // do animal-only logic here
    }
    else
    {
        // do logic for both here
    }

    return summary.ToString();
}

Of course my example is overly simple and contrived. For instance, either as part of the logic in your Summarize() if/else statements, or perhaps surrounding the entire block, you'll want to iterate over the displayableItems collection. Also, you'll likely get better performance if you override Add() and Remove() in MyCollection and have them check the type of the object and set a flag, so your containsHumans() function (and others) can simply return the state of the flag and not have to iterate the collection every time they're called.

rmeador
Thanks for you reply, but I am not entirely understanding the technicality, so could you give me an example of it? :) thanks again, this might be exactly what I need. I just need to see an example of it.
SP
hmm... does the fact that you accepted it mean you don't need an example? I'd be happy to try to provide one, but I'm not quite sure which part is causing you confusion. If you could be more specific, I'll give it a try.
rmeador
I accepted it as an answer, because it answered my specific question. I am working on a solution based on this. I just need an example to make sure I understand the technicality. Justin's answer below was very elaborate and helpful, so something similar to it would be awesome.
SP
@SP: I added an example... let me know if I can help further.
rmeador
Thank you so much! It certainly makes things much cleaner. One question I have is that Summarize() is now tied to MyCollection class. I guess I am becoming too much paranoid about this, but can I have something like IMyCollection interface so that Summarize() is not tied to the concrete MyCollection implementation? Again, thank you for your help!
SP
@SP: of course you can introduce an interface like you describe.
rmeador