C# 选择性方法可见性的设计模式

C# 选择性方法可见性的设计模式,c#,methods,visibility,C#,Methods,Visibility,考虑以下代码 class SqlInstance { private SqlInstance() { } public void Connect(string username, string password) { //connect } public void Disconnect() { //disconnect } //This method is not a singlet

考虑以下代码

class SqlInstance
{
    private SqlInstance()
    {

    }
    public void Connect(string username, string password)
    {
        //connect
    }
    public void Disconnect()
    {
        //disconnect
    }
    //This method is not a singleton. Its one instance per key
    public static SqlInstance GetInstance(string key)
    {
        return new SqlInstance();
    }
}

class FileInstance
{
    private FileInstance()
    {

    }
   //no this is not a mistake. This connect has no parameters
    private void Connect()
    {
        //connect
    }
    public void Disconnect()
    {
        //disconnect
    }
    //This method is not a singleton. Its one instance per key
    public static FileInstance GetInstance(string key)
    {
        return new FileInstance();
    }
}


class DataManager
{
    SqlInstance GetSqlChannelInstance()
    {
        //if some logic
        return SqlInstance.GetInstance("dev.1");

        //if some other logic
        return SqlInstance.GetInstance("dev.2");

        //...and so on
    }

    FileInstance GetFileInstance()
    {
        //if some logic
        return FileInstance.GetInstance("fil.1");

        //if some other logic
        return FileInstance.GetInstance("fil.2");

        //...and so on
    }
}
DataManager是一个包装器样式的类,调用者必须使用它来获取SqlInstanceFileInstance的实例。这里的问题是调用方可以直接调用类的GetInstance方法,而不是通过DataManger类。我们如何解决这个问题?具体来说,是否有一种模式或机制可以强制调用者通过数据管理器?是否可以使两个实例类仅对数据管理器类“可见”

我知道将这两个类作为DataManager类的内部类将有助于解决这个问题,但我想知道是否还有其他“更好”的方法可以做到这一点

PS:请忽略类名和实现。这只是一个示例,并非取自任何实际代码。

语言是C#我的C#已生锈,但如果调用方代码位于不同的汇编文件中(即,您的代码导入到调用方代码中),您可能可以尝试使用
内部
修饰符

编辑:找到一个更合适的:
InternalVisibleTo


嵌套SqlInstance和FileInstance的替代方法-GetInstance对其他类型可见(但它们的签名意味着DataManager与它们关联),并且只要DataManager不公开令牌,同一程序集中只有DataManager和其他类型才能获取FileInstance和SqlInstance的实例

public class SqlInstance
{
    private SqlInstance() {}

    internal static SqlInstance GetInstance(DataManager.Token friendshipToken, string key)
    {
        if (friendshipToken == null)
            throw new ArgumentNullException("friendshipToken");
        return new SqlInstance();
    }
}

public class FileInstance
{
    private FileInstance() {}

    internal static FileInstance GetInstance(DataManager.Token friendshipToken, string key)
    {
        if (friendshipToken == null)
            throw new ArgumentNullException("friendshipToken");
        return new FileInstance();
    }
}

public class DataManager
{
    private static Token token;

    static DataManager()
    {
        Token.SetToken();
    }

    public class Token
    {
        private Token() {}

        public static void SetToken()
        {
            token = new Token();
        }
    }

    public SqlInstance GetSqlChannelInstance()
    {
        return SqlInstance.GetInstance(token, "dev.1");
    }

    public FileInstance GetFileInstance()
    {
        return FileInstance.GetInstance(token, "fil.1");
    }
}

因为调用者必须能够看到返回对象的类型,所以在这个版本的代码上只使用internal是行不通的,正如另一个答案中所述

我们可以通过创建一个接口或Abstract类(这可能是后者可能更可取的少数情况之一)来解决这个问题,SqlInstance和FileInstance从中下降。然后我们可以对调用方隐藏这些具体实现

interface AbstractInstance {
    // Some stuff here    
}



internal class SqlInstance : AbstractInstance {

    // Ideally nothing changes here

}

internal class FileInstance : AbstractInstance {

    // Ideally nothing changes here

}
使用
internal
,具体类的可见性可以限制在它们所在的程序集的范围内,而接口可以全局使用。 现在我们只需要更改工厂,使其返回值依赖于抽象而不是实现

 class DataManager
    {
        AbstractInstance GetSqlChannelInstance()
        {
            //if some logic
            return SqlInstance.GetInstance("dev.1");

            //if some other logic
            return SqlInstance.GetInstance("dev.2");

            //...and so on
        }

        AbstractInstance GetFileInstance()
        {
            //if some logic
            return FileInstance.GetInstance("fil.1");

            //if some other logic
            return FileInstance.GetInstance("fil.2");

            //...and so on
        }
    }
显然,任何依赖于具体实现的调用代码此时都可能停止工作

让我知道它是否有效,顺便说一下:D

class SqlInstanceManager:SqlInstance
    {
        private SqlInstanceManager(){ }
        public static new GetInstance()
        {
            return SqlInstance.GetInstance("key");
        }
    }
class SqlInstance
        {
            protected SqlInstance()
            {

            }
            public void Connect(string username, string password)
            {
                //connect
            }
            public void Disconnect()
            {
                //disconnect
            }
            //Make this protected. Now this class cannot be instantiated
            //and it cannot be called without inheriting this class
            //which is sufficient restriction.
            protected static SqlInstance GetInstance(string key)
            {
                return new SqlInstance();
            }
        }

       //And the same thing for FileInstance



     class DataManager
        {
            SqlInstance GetSqlChannelInstance()
            {
                //if some logic
                return SqlInstanceManager.GetInstance("dev.1");

                //if some other logic
                return SqlInstanceManager.GetInstance("dev.2");

                //...and so on
            }


        }
现在调用者可以调用SqlInstance上除GetInstance之外的所有方法,没有人可以直接调用SqlInstance上的GetInstance

这也解决了另一个意想不到的问题:以前返回的SqlInstance可能会进一步调用自身的GetInstance,从而破坏工厂的整体功能


感谢Dek Dekku让我朝着正确的方向思考。

您的编程语言是Java?@BobCromwell它是C#这不起作用,因为GetSqlChannelInstance需要返回调用方可见的SqlInstance。创建一个接口或Abstract类,SqlInstance和FileInstance从该接口或Abstract类下降,并且只让调用方看到该接口或Abstract类,同时隐藏实现,怎么样?(顺便说一句,你的
InternalVisibleTo
是对的,但是
internal
应该在方法上起作用)这是个好主意。如果你能加上它作为回答,我将很乐意接受。请确保它足够详细,让每个人都能理解。我不想自己创建一个答案,并在我从您那里得到这个想法后标记它。您可以将类设置为公共类,并将CreateInstance方法设置为内部。如果类SqlInstance和FileInstance是公共类,但具有内部GetInstance,您将获得相同的结果—其他程序集中的任何类型都不能调用GetInstance—但依赖具体实现不会有问题。这并不能解决问题-来自同一程序集的类型仍然可以调用FileInstance的GetInstance。@Dek Dekku:Lisp是正确的。看看我的答案,看看我做了什么。我以为你就是这个意思。此外,并不总是可以将类移动到单独的程序集。