简体   繁体   中英

What is the best way to design this class hierarchy?

I have a database table which contains an ID column and a Name column. I am tasked with designing a program that accepts one of the IDs as an argument to Main() .

Bold is edit 2

I need to use that ID which must exist in the database, to correspond to some code to run . Each row in the table corresponds to slightly different code, but a lot of them share a lot of code. I need a design that will minimize code duplication.

So far what I've developed is an abstract base class that has an abstract Int32 field ID to enforce derived classes having their corresponding ID in the database. That way I can reflect over the derived classes to find the one whose ID matches the Main() argument and instantiate that class. Then I just call the virtual methods from Main() which runs the most derived code that has been defined.

public abstract class Base {
    public abstract Int32 Id { get; }    
    public void Foo() {
        // Do something
    }
}

public class Derived {
    public override Int32 Id { get { return 42; } } 
    public void Foo() {
        // Do something more specific
    }
}

Does anyone have any better ideas how to achieve what I want? I like the idea of keeping the ID right in the class definition, but I'm open to changing that if it makes sense.

Thanks!

EDIT:

One thing I don't like about this is that I have to reflect over each derived type and instantiate that type to check the ID. Does anyone have a better idea on how to do that?

Instead of using a property to define the ID of the class, use a custom attribute. That way, you don't have to instantiate the object to check what its ID is.

When your program runs, it can scan the assembly for all classes with that attribute tag, and find the one with the matching ID, instantiate that class, and then run it's Foo method. If you perform this kind of lookup multiple times per application run, you could instatiate all the classes with your custom attribute and then put them into a Dictionary to provide quick lookups by ID.

Your code might look something like this:

[AttributeUsage(AttributeTargets.Class)]
public class CommandAttribute {
    public CommandAttribute(int id) {
        ID = id;
    }

    public int ID { get; private set; }
}

public abstract class Command {
    public abstract void Execute();
}

[Command(2)]
public class MyCommand : Command {
    public override void Execute() {
        //Do something useful
    }
}

The other advantage of using a custom attribute is that you have to explicitly tag everything that is a candidate for being instantiated and executed by ID, rather than assuming than anything derived from your base class is a candidate. If you are sharing code between the classes, you might want to make a common base class for them that derives from your base class, but should not be instantiated or executed on its own.

One thing I don't understand is, what is the point of the "Name" field if the class you want to run is identified by the ID? If you can decide what the name of each ID is, then you could use the name field as the fully qualified type name of the class you want to execute, which then avoid having to scan through all the types in your assembly (or application domain, depending upon the scope of your search). That setup is a bit more prone to typos, however.

It sounds like you need to implement a factory pattern.

I would define an interface:

public interface IWidget
{
    void Foo();
}

Then the base class:

public abstract class WidgetBase : IWidget
{
    public void Foo()
    {
        this.Bar()
    }

    protected virtual void Bar()
    {
        // Base implementation
    }
}

The factory:

public static WidgetFactory
{
    public static IWidget Create(int id)
    {
        // Get class name from id, probably use the name in your database.
        // Get Type from class name
        // Get constructor for Type
        // Create instance using constructor and return it.
    }
}

A derived class:

public class DerivedWidget : WidgetBase
{
    protected override void Bar()
    {
        // call base implementation
        base.Bar();
        // derived implementation
    }
}

In your main:

public void Main(int id)
{
    var widget = WidgetBase.Create(id);
    widget.Foo();
}

I like @Xint0's idea of using a Factory for this kind of task, but I thought I'd still contribute another answer.

A better way to implement your original design would be to pass the ID to the base constructor as follows:

public abstract class Base {
    public Int32 Id { get; private set; }
    protected Base(Int32 id) {
            this.Id = id;
    }
    public void Foo() {
        // Do something
    }
}

public class Derived : Base {
    public Derived : base(42) {}
    public void Foo() {
        // Do something more specific
    }
}

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