1

I expect this is a common problem and/or pattern question for beginners to OOP. Somehow I find myself wondering again and wasn't able to find a decent answer. So at the risk of creating a dupe...

What is the best practice or pattern to follow when a class must behave slightly differently in a given context?

I am asking specifically about differences to behavior that lie within the line by line method operations and not an entire method of my class. So I'd assume implementing two classes against a common interface with different methods may cause a lot of redundancy but perhaps not.

That said here is my dummy oversimplified C# example of an approach I've seen in the wild. Assume this class could grow to 2000+ lines and have slight differences within various private methods throughout.

using System;

namespace ClassBahaveMod
{
    class Program
    {
        static void Main(string[] args)
        {
            bool isAlternateContext = args.Length >= 2 && args[1] == "1" ? true : false;

            var fooService = new FooService();
            if (isAlternateContext)
            {
                fooService.IsAlternateBehavior = true;
            }

            fooService.DoThing();
        }
    }

    public class FooService
    {
        public bool IsAlternateBehavior { get; set; }

        public void DoThing() 
        {
            Console.WriteLine("Common Action 1");

            if (IsAlternateBehavior)
            {
                Console.WriteLine("Alternate Action");
            }
            else
            {
                Console.WriteLine("Original Action");
            }

            Console.WriteLine("Common Action 2");
        }
    }
}

So with the above approach a "global" like and public property IsAlternateBehavior is exposed to flag how the method should flow.

Many questions come to mind for me when looking at this implementation. My initial reaction is that this is not a great way to handle this but it works. Is this a known pattern/anti-pattern or bad practice and if so what might an alternative implementation look like? Or is this public flag just fine for many cases?

  • 1
    You are right, this is a common problem. Changing behavior of something based on something else actually describes 99% of code in general. There are many, many ways to deal with this, depending on the context of the problem. The way you're doing it is one way; you could also pass in a parameter, a context object, use a factory to provide different a different class depending on a condition, pass in a a lambda or delegate, use a strategy pattern, etc. This question is too broad to be answered except as a matter of opinion.. – John Wu Jun 19 at 17:01
3

I am asking specifically about differences to behavior that lie within the line by line method operations and not an entire method of my class.

That distinction really doesn't matter. That specific line can be wrapped into a method of its own. Given that it behaves differently than the surrounding lines (since this line's behavior can be branched, but the other common logic doesn't change), putting it into a method of its own is warranted.

So I'd assume implementing two classes against a common interface with different methods may cause a lot of redundancy

Using common logic in an ancestor (class, not interface) specifically avoids redundancy.

Is this a known pattern/anti-pattern or bad practice and if so what might an alternative implementation look like?

There are two possibilities here, and in either case there's a better approach. Either:

  • A single foo service must be able to perform both behaviors.
  • A single foo service will always have one given behavior

On to the implementation:

A single foo service must be able to perform both behaviors

In this case, the two behaviors are distinct behaviors and should be portrayed using separate public methods.

Internally, you can use your boolean toggle if you like, but make sure that it is an appropriate solution. It is technically possible to keep one public method and have it take in a boolean parameter, i.e. public void DoThing(bool useAlternateBehavior) and deleting the other methods; but boolean flags are generally advised against and should be avoided where possible.

public class FooService
{
    public void DoThingOne() 
    {
        DoCommonThing1();
        Console.WriteLine("Original Action");
        DoCommonThing2();
    }

    public void DoThingTwo()
    {
        DoCommonThing1();
        Console.WriteLine("Alternate Action");
        DoCommonThing2();
    }

    private void DoCommonThing1()
    {
        Console.WriteLine("Common Action 1");
    }

    private void DoCommonThing2()
    {
        Console.WriteLine("Common Action 2");
    }
}

Whether or not you need to separate these common actions very much depends on whether the order of your three operations (common 1, original/alternate, common2) matters or not. If it doesn't matter, then just lump all common behavior into a single DoCommonThing() method.

A single foo service will always have one given behavior

Here, we enter the realm of abstraction, inheritance and overriding. Your two services have a common contract, but also have a partially specific implementation.

public abstract class BaseFooService
{
    public abstract void DoSpecificBehavior();

    public void DoThing() 
    {
        Console.WriteLine("Common Action 1");

        DoSpecificBehavior();

        Console.WriteLine("Common Action 2");
    }
}

public class FooService1 : BaseFooService
{
    public override void DoSpecificBehavior()
    {
        Console.WriteLine("Original Action");
    }
}

public class FooService2 : BaseFooService
{
    public override void DoSpecificBehavior()
    {
        Console.WriteLine("Alternate Action");
    }
}

You define the common code in a common ancestor. If this particular method contains a lot of common logic and only a small portion of it is specific to each service, then you abstract that specific logic in an abstract method, which forces derived classes to provide this implementation.

Your base class calls the method without knowing exactly what it's going to do. It can be sure that the compiler forced any derived classes to provide an implementation for this abstract method, but each derived class gets the freedom to implement it their own way.

I'm skipping over the distinction between where and when you should use abstract and virtual methods. Based on your current example, abstract seems to be the most applicable.

| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.