设计模式:选择性方法可见性

3
考虑以下代码
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方法,而不是通过DataManager类。我们如何解决这个问题?具体来说,有没有一种模式或机制强制调用者通过DataManager呢?是否可能将这两个Instance类仅“公开”给DataManager类?
我知道将这两个类作为DataManager类的内部类可以帮助解决问题,但我想知道是否有其他“更好”的方法来解决这个问题? PS:请忽略类名和实现。这只是一个示例,不是从任何实际代码中提取的。 语言是C#。

你的编程语言是Java吗? - Bob Cromwell
@BobCromwell 这是 C#。 - bobbyalex
4个回答

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让我朝着正确的方向思考。

0

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

0

由于调用者必须能够看到返回对象的类型,所以在此代码版本中仅使用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,具体类的可见性可以限制在它们所在的程序集范围内,而接口可以全局使用。 现在我们只需要更改工厂,使其返回值依赖于抽象而不是实现。
 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。 - lisp
1
@Dek Dekku:Lisp 是正确的。请看我的回答以了解我所做的。我认为那就是你的意思。另外,将类移动到单独的程序集中并不总是可行的。 - bobbyalex

0

这样做不行,因为GetSqlChannelInstance需要返回SqlInstance,而且应该对调用者可见。否则代码将无法编译。 - bobbyalex
创建一个接口或抽象类,让SqlInstance和FileInstance继承它,只向调用者公开该接口或抽象类,同时保持实现的隐藏状态,这个想法怎么样?(顺便说一句,“InternalVisibleTo”是正确的,但“internal”也可以用于方法。) - Dek Dekku
这是一个很好的想法。如果您能将其作为答案添加,我将非常乐意接受。请确保它足够详细,以便每个人都能理解。我不想自己创建答案,然后在从您那里得到灵感后标记它。 - bobbyalex
你可以将类设为public,而CreateInstance方法则设为internal。 - k3b

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接