簡體   English   中英

使用泛型為C#中的可調后端鍵入安全性?

[英]Type safety with generics for modulable backends in C# ?

考慮編寫一個依賴於后端的軟件,該后端必須是模塊化的。 只要后端適合單個類,通過定義一些接口並讓幾個后端類實現它很容易實現:

interface IBackend { ... }
class BackendA : IBackend { ... }
class BackendB : IBackend {...}

現在,假設使用后端需要保存中間數據,其內部依賴於后端。 同樣,它由接口和具體類表示:

interface IFoo { ... }
class FooA : IFoo { ... }
class FooB : IFoo { ... }

Foo的構造函數沒有公開,我們可以依賴IBackend接口中的工廠方法:

interface IBackend {
    IFoo createFoo(some arguments)
}

class BackendA : IBackend {
    override FooA createFoo(some arguments) {
        return new FooA(some arguments)
    }
}

}

使用來自公共抽象層的接口無處不在,但現在BackendA的代碼現在被迫在需要時接受IFoo而不是FooA ,並將其轉換為FooA

interface IBackend {
    void soSomethingWithFoos(IFoo f1, IFoo f2);
}
class BackendA : IBackend {
    override void doSomethingWithFoos(IFoo f1, IFoo f2) {
        FooA fa1 = (FooA)f1;
        FooA fa2 = (FooA)f2;
        // do something
    }
}

此外,這不是類型安全的,因為粗心的用戶可以立即實現不同的后端,並在編譯器的鼻子下將對象從一個后端傳遞到另一個后端:

BackendA ba = new BackendA();
BackendB bb = new BackendB();
ba.doSomethingWithFoo(bb.createFoo(some args)); // Typechecks but is clearly incorrect

為了改善這一點,我們可以為所有常見接口添加一個類型參數: IBackend<T>IFoo<T>等...並用它來區分后端: BackendA : IBackend<A>BackendB : IBackend<B>FooA : IFoo<A> 然后我們可以:

interface IBackend<T> {
    public IFoo<T> createFoo(some arguments)
}

這樣,編譯器就不允許混合來自不同后端的對象。 然而,這並不十分令人滿意:來自某些后端的代碼BackendA仍然必須將IFoo<A>FooA ,即使這是類型安全的,只要FooA是實現IFoo<A>的唯一類。 此外,願意獨立於后端的代碼現在已經遍布整個地方的泛型。

為了解決這個問題,如果我們假設我們需要表示后端的類數量是有限且常量的,我們可以通過使用所有具體類參數化接口來避免強制轉換:

interface IBackend<T, FooT, BarT> where FooT : IFoo<T>, BarT : IBar<T> {
    FooT createFoo();
    ...
}

我甚至不知道這是否是有效的語法,也不知道這是否真的解決了問題。

這是瘋了嗎? 那里有一種類型安全的方式嗎? 或者沒有使用泛型有更好的方法嗎?

您可以通過稍微顛倒角色來解決這個問題。 而不是讓IBackend像這樣采用IFoo並相信IFoo總是與相應的IBackend ,例如

interface IBackend {
    IFoo createFoo();
    void doSomethingWithFoo(IFoo foo);
}

您可以更改此項以讓IFoo記住它來自哪個IBackend

interface IBackend {
    IFoo createFoo();
}

interface IFoo {
    void doSomethingWithBackend();
}

class BackendA : IBackend {
    IFoo createFoo() {
         return new FooA(this);
    }

    void doSomethingWithFoo(FooA foo) { ... }
}

class FooA : IFoo {
    private BackendA backend;

    FooA(BackendA backend) {
        this.backend = backend;
    }

    void doSomethingWithBackend() {
        backend.doSomethingWithFoo(this);
    }
}

如果您的設計應該按照您的描述進行模塊化,則演員應該完全沒必要。 演員(通常)是低於模塊化系統的標志。

為了使事物完全模塊化,接口IFoo應捕獲使用IFoo實現實例所必需的操作。 棘手的部分是為您的問題找到足夠高的抽象。 在那之后,提供一個表示該抽象的接口變得微不足道。 當然,這通常說起來容易做起來難,但如果你正在尋找完全類型安全的模塊化,那么我認為這是最佳選擇。

你想要的是一種叫做“虛擬類型”的東西,它基本上使類型參數成為可以被派生類覆蓋的虛擬東西。 用於推廣這一想法的典型用例是當您擁有一系列類型時,所有類型都必須一致地覆蓋。

他們看起來像這樣:

class Foo {
    virtual type T = Bar;
    virtual type R = Baz;

    abstract T CreateT() {
    }
}

class FooDerived : Foo {
    override type T = BarDerived;
}

當你在foo上使用類型成員T時,你會看到一個Bar。 在FooDerived上,您會看到BarDerived。 這對程序員來說非常方便。

顯然C#沒有這個功能。

我所知道的支持虛擬類型的一種語言是Beta。

你可以在這里讀到它:

http://en.Wikipedia.org/wiki/BETA

要帶走的一件重要事情是,這不是靜態類型安全的。 當編譯器支持它時,它需要大量的運行時檢查才能實際上是類型安全的。

最接近靜態類型的等價物將是變體接口(C#支持),但是對於每個“實體”類型,您將需要一個類型參數,這將是不實用的。 引用接口會非常嘈雜( IBackend<T1,...,T10> )。 更重要的是,variant參數既可以用作參數,也可以用作返回類型。 不是都。 鑒於您的示例代碼,這對您不起作用。

這是我的主要觀點。 你想要做的是固有的動態類型。 所以,我不會嘗試靜態輸入。 這只是讓事情變得比他們需要的更復雜。 嘗試更有活力。 這可能會簡化后端和實體接口。 此外,您的后端界面似乎有點“健談”。 這通常表示“非最佳抽象”。 最好的界面是廣泛而簡單的想想IEnumerable或IDisposable。

理想情況下,您的后端界面應如下所示:

interface IBackend {
    void Run(IFrontEndStuff);
}

我不確定這是否是您正在尋找的,但一種方法可能是一個抽象基類Foo,然后是一個動態調度到具體處理程序的處理程序。 就像是:

void FooHandler(dynamic foo)
{
   FooHandlerImpl(foo);
}

void FooHandlerImpl(FooA foo)
{
   //whatever you do with FooA
}

void FooHandlerImpl(FooB foo)
{
   //whatever you do with FooB
}

如何使用謹慎的接口來分離關注點 (松耦合)

像這樣的東西......

class Program
{
    static void Main(string[] args)
    {
        IBackend addon = new FooA();

        Console.WriteLine("Enter something if you like");
        var more = Console.ReadLine();

        var result = Runtime(addon);
        Console.WriteLine("Result: {0}", result ?? "No Output :o(");
    }

    static object Runtime(IBackend addon, string more = null)
    {
        var need = addon as INeed;
        if (need != null)
            need.Input = more;

        addon.Execute();

        var give = addon as IGive;
        if (give != null)
            return give.Output;

        return null;
    }
}

public interface IBackend
{
    void Execute();
}
public interface INeed
{
    string Input { set; }
}
public interface IGive
{
    string Output { get; }
}
public class FooA : IBackend, INeed, IGive
{
    public void Execute()
    {
        Console.WriteLine(this.Input ?? "No input :o(");

        if (!string.IsNullOrWhiteSpace(this.Input))
            this.Output = "Thanks!";
    }

    public string Input { private get; set; }
    public string Output { get; private set; }
}

如果您的加載項/后端項不提供接口,那么應用程序的該部分將忽略它。 你可以用泛型來收緊這個問題,但我不確定這是否真的有必要。

使用集合和委托,您甚至可以使其更加抽象,從而提高運行時靈活性。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM