简体   繁体   中英

Polymorphism issue with persistence layer and business logic call c#

I am actually learning polymorphism. I have my business logic completely decoupled of the persistence layer using the DAO pattern, but now I am facing a problem when I am trying to access the business data in the persistence layer.

I will bring you a simple example

public class A
{

}

public class B : A
{

}

public class C : A
{

}

public class D : A
{

}

class Program
{
    protected virtual void Main(string[] args)
    {
        List<A> alist = new List<A>
        {
            new B(),
            new C(),
            new D()
        };

        PersistenceLayer persistenceLayer = new PersistenceLayer();
        foreach (A a in alist)
        {
            persistenceLayer.Foo(a); // can't call it
        }


    }

    public class PersistenceLayer
    {
        public void Foo(B b)
        {
            Console.WriteLine("is B");
        }

        public void Foo(C c)
        {
            Console.WriteLine("is c");
        }

        public void Foo(D d)
        {
            Console.WriteLine("is d");
        }
    }
}

I need to loop around the generic class, that is what I have in a list form, that is what I can obtain by my business class controller. Now to get the subclass type and call the appropriate method I need to change the foreach for the next.

foreach (A a in alist)
{
    if (a is B b)
    {
        persistenceLayer.Foo(b);
    }
    else if (a is C c)
    {
        persistenceLayer.Foo(c);
    }
    else if (a is D d)
    {
        persistenceLayer.Foo(d);
    }
}

So now is it working, but I hate the switch or the ifs checking the type and calling methods because now I have 3 subclasses, but what happens with 20 subtypes?

Is there any way to do this without doing this ifs or switch? Is there some pattern maybe?

This looks like a perfect use case for the Visitor pattern .

public interface IPersistenceLayer
{
    // These methods could all be called 'Foo' without the 'A' 'B' or 'C' suffix, but I've appended it to make it clear which method is being called
    void FooB(B b);
    void FooC(C c);
    void FooD(D d);
}

// I've made 'A' abstract, because in your example there is no 'Foo(A a)' method so this can't provide a default 'Foo' implementation
public abstract class A
{
    public abstract void Foo(IPersistenceLayer persistenceLayer);
}

public class B : A
{
    public override void Foo(IPersistenceLayer persistenceLayer) => persistenceLayer.FooB(this);
}

public class C : A
{
    public override void Foo(IPersistenceLayer persistenceLayer) => persistenceLayer.FooC(this);
}

public class D : A
{
    public override void Foo(IPersistenceLayer persistenceLayer) => persistenceLayer.FooD(this);
}

public static class PersistenceLayerExtensions
{
    public static void Foo(this IPersistenceLayer persistenceLayer, A a) => a.Foo(persistenceLayer);
}

class Program
{
    public static void Main(string[] args)
    {
        List<A> alist = new List<A>
        {
            new B(),
            new C(),
            new D()
        };

        PersistenceLayer persistenceLayer = new PersistenceLayer();
        foreach (A a in alist)
        {
             persistenceLayer.Foo(a);
        }
    }
}

You could add a function to the Persistence layer that accepts the type A, then checks which one it is and calls the appropriate one. This is better since you don't have to check which one it is every time, only once inside of the function.

It would look something like this:

public void Foo(A a)
{
  if(a.GetType() == typeof(B))
  {
    Foo((B) a);
  }
  else if(a.GetType() == typeof(C))
  {
    Foo((C) a);
  }
  else if(a.GetType() == typeof(D))
  {
    Foo((D) a);
  }
}

so this is basically a general method which you can call with any object inheriting from A.

This is solution complies with the DRY (Don't Repeat Yourself) principles.

If the idea is only to print which one it is you could also do it as follows:

public void Foo(A a)
{
  Console.WriteLine($"is {a.GetType().toString()}");
}

I used string interpolation here but you can also go without, it just looks a bit better if you ask me

And if you are a fancy man you can also add the function to the classes themselves as follows:

public abstract class A
{
  public abstract void Foo();
}

public class B : A
{
  public override void Foo()
  {
    Console.WriteLine("is b");
  }
}

public class C : A
{
  public override void Foo()
  {
    Console.WriteLine("is c");
  }
}

public class D : A
{
  public override void Foo()
  {
    Console.WriteLine("is d");
  }
}

Note that I made class A abstract here, since you don't actually make instances of A itself you can make the class abstract and only provide a 'template' for the actual functions which the inheriting members then override .

I hope this helped, Good luck on your c# journey

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM