从同一个方法返回不同类型

问题描述 投票:0回答:2

我有两个相似但不完全相同的对象:

public class ObjectA
{
    public string PropertyA { get; set; }
}

public class ObjectB
{
    public string PropertyA { get; set; }
    public string PropertyB { get; set; }
}

在这两个对象中,

PropertyA
用于通过存储过程进行数据库查找,但是,对象之间使用的存储过程有所不同。假设存储过程成功返回,响应将用于创建相关对象。

目前我有两种方法执行相同的逻辑:调用相关存储过程然后创建对象:

public ObjectA QueryObjectA(string propertyA)
{
    // shared code

    var result = CallToStoredProcedureA(...);
    var obj = new ObjectA
    {
        // use result here
    };

    return obj;
}

public ObjectB QueryObjectB(string propertyA)
{
    // shared code

    var result = CallToStoredProcedureB(...);
    var obj = new ObjectB
    {
        // use result here
    };

    return obj;
}

这是可行的,但是,我在多个地方维护一些共享代码,如果我将来要添加另一个对象,我可能会有第三种方法(

QueryObjectC
)。

这个可以或者应该通过使用泛型来改进吗?如果可以的话会是什么样子?也许类似于以下内容?

public T QueryObject<T>(string propertyA) where T : new()
{
    // shared code

    // get type of T
    // switch statement that calls the relevant stored procedure and creates the object

    // return object
}
c# generics asp.net-web-api asp.net-web-api2
2个回答
1
投票

因此,无论哪种方式,您可能都希望在 A 和 B 之间创建一个公共接口。我们将其称为

IBase

现在最简单的解决方案是尝试将此过程移至内部

IBase
,让多态性为您完成艰苦的工作

public T QueryObject<T>() where T: IBase, new() 
{
    var obj = new T();
    var result = obj.DoProcedure(...);
    obj.AssignResults(result); // could be a part of DoProcedure
    return obj;
} 

interface IBase
{
    ResultType DoProcedure(...);
} 

每个 A 或 B 都会实现正确的调用(如果需要,可能使用访问者模式)

但是,如果这个过程确实不是 A 或 B 的责任(从表面上看可能是,但如果不是),那么您可以升级到某种工厂的稍微更重型的解决方案。

您需要一个与以前类似的界面,但这个界面可以留空

interface IBase
{ } 

还有一个

IProcedureExecutor
界面(名字可以改进):

interface IProcedureExecutor
{
    ResultType DoProcedure(...);
} 

实现者将各自实现该功能,

ATypeProcExecutor : IProcedureExexutor
BTypeProcExecutor : IProcedureExexutor

然后是工厂:

class ProcedureExecutorFactory
{
    private static Dictionary<Type, IProcedureExecutor> executors = new
    {
        {typeof(A), new ATypeProcExecutor}, 
        {typeof(B), new BTypePrpcExecutor} 
    }; 

    public static IProcedureExecutor GetExecutor(Type t)
    {
        return executors[t];
    } 
} 

那么你的

QueryObject
函数将是这样的:

public T QueryObject<T>() where T: IBase, new() 
{
    var executor = ProcedureExecutorFactory.GetExecutor(typeof(T));
    var result = executor.DoProcedure(...);

    var obj = new T();
    obj.AssignResults(result);// could be part of constructor
    return obj;
}  

第二个可能更可靠,但对于你的问题来说可能有点过分了。

您在问题的伪代码中提到了

switch
语句,这是一个坏主意。基于
T
类型进行切换的泛型函数实际上完全失去了使用泛型所获得的所有灵活性优势。

想要根据对象的类型做不同的事情确实是 OOP 的核心,因此充分利用多态性确实是正确的方法。

(在我的手机上,所以代码未经测试)


0
投票

通过使用具有属性

ObjectBase
的通用抽象基类
PropertyA
,您可以编写

public T QueryObject<T>(string propertyA) where T : ObjectBase, new()
{
    var result = typeof(T).Name switch {
        nameof(ObjectA) => CallToStoredProcedureA(...),
        nameof(ObjectB) => CallToStoredProcedureB(...),
        _ => throw new NotImplementedException()
    };
    var obj = new T {
        PropertyA = result
    };

    return obj;
}

如果不同对象类型的初始化不同,那么您可以考虑为这些类型添加静态工厂方法。

具有工厂方法的对象:

public class ObjectA : ObjectBase
{
    public static ObjectA Create(string storedProcResult) =>
        new() { PropertyA = storedProcResult };
}

public class ObjectB : ObjectBase
{
    public string PropertyB { get; set; }

    public static ObjectB Create(string storedProcResult) =>
        new() {
            PropertyA = storedProcResult.Substring(0, 3),
            PropertyB = storedProcResult.Substring(3)
        };
}

使用工厂方法的通用方法:

public T QueryObject<T>(string propertyA) where T : ObjectBase, new()
{
    ObjectBase obj =  typeof(T).Name switch {
        nameof(ObjectA) => ObjectA.Create(CallToStoredProcedureA(...)),
        nameof(ObjectB) => ObjectB.Create(CallToStoredProcedureB(...)),
        _ => throw new NotImplementedException()
    };
    return (T)obj;
}
© www.soinside.com 2019 - 2024. All rights reserved.