【问题标题】:Design pattern for selective method visibility选择性方法可见性的设计模式
【发布时间】:2013-05-03 13:30:20
【问题描述】:

考虑下面的代码

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 的模式或机制?是否可以使两个 Instance 类仅对 DataManager 类“可见”。

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

PS:请忽略类名和实现。这只是一个示例,并非来自任何现实生活中的代码。

语言是 C#

【问题讨论】:

  • 您的编程语言是 Java 吗?
  • @BobCromwell 它是 C#

标签: c# methods visibility


【解决方案1】:
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 让我朝着正确的方向思考。

【讨论】:

    【解决方案2】:

    由于调用者必须能够看到返回对象的类型,因此仅在此版本的代码上使用 internal 是行不通的,如另一个答案中所述。

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

    interface AbstractInstance {
        // Some stuff here    
    }
    
    
    
    internal class SqlInstance : AbstractInstance {
    
        // Ideally nothing changes here
    
    }
    
    internal class FileInstance : AbstractInstance {
    
        // Ideally nothing changes here
    
    }
    

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

     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

    【讨论】:

    • 如果类 SqlInstance 和 FileInstance 是公共的但具有内部 GetInstance,您将实现相同 - 其他程序集中的任何类型都不能调用 GetInstance - 但不存在依赖于具体实现的问题。这并不能解决问题 - 来自同一个程序集的类型仍然可以调用 FileInstance 的 GetInstance。
    • @Dek Dekku:Lisp 是对的。看看我的回答,看看我做了什么。我以为这就是你的意思。此外,并非总是可以将类移动到单独的程序集中。
    【解决方案3】:

    嵌套 SqlInstance 和 FileInstance 的替代方法 - GetInstance 对其他类型可见(但它们的签名暗示 DataManager 与它们相关联)并且只有 DataManager 和同一程序集中的其他类型无法获取 FileInstance 和 SqlInstance 的实例,只要 DataManager 不暴露 Token。

    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");
        }
    }
    

    【讨论】:

      【解决方案4】:

      我的 C# 生锈了,但如果调用者代码位于不同的程序集文件中(也就是说,您的代码是在调用者代码中导入的),您可能可以尝试使用 internal 修饰符。

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

      http://msdn.microsoft.com/en-us/library/system.runtime.compilerservices.internalsvisibletoattribute.aspx

      【讨论】:

      • 这不起作用,因为 GetSqlChannelInstance 需要返回调用者应该可见的 SqlInstance。否则代码将无法编译。
      • 如何创建一个 SqlInstance 和 FileInstance 下降的接口或抽象类,并且只使调用者可以看到该类,同时保持实现隐藏? (顺便说一句,你适合 InternalVisibleTointernal 应该在方法上工作)
      • 这是个好主意。如果您可以将其添加为答案,我将很乐意接受。请确保它足够详细,让每个人都能理解。我不想自己创建一个答案并标记它,所以在我从你那里得到想法之后。
      • 您可以将类公开,并将 CreateInstance-Method 设为内部。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-02-28
      • 2019-11-06
      • 2012-05-08
      • 1970-01-01
      • 1970-01-01
      • 2011-08-10
      相关资源
      最近更新 更多