【问题标题】:Implement interface without publicly exposing property setter?在不公开属性设置器的情况下实现接口?
【发布时间】:2020-01-04 19:33:46
【问题描述】:

下面的示例代码更好地解释了我的问题,它在 cmets 中得到了很好的解释。 Browser 包含 BrowserModule 的各种实现者,这些模块具有对所有者 Browser 的引用,因此它们可以在内部协同工作。每个“主”模块内部都有一个 SpecificModule,这些“主”模块仅充当将实现目标浏览器自动化库(如 Selenium 或 Puppeteer 等)的特定模块的代理......

例如,对于 BrowserMouse 模块,它将 SpecificModule 实例化为 BrowserPuppeteerChromiumSpecificMouse 并将调用委托给该特定模块。

我不希望 Browser 类的用户能够访问每个自动化库的底层实现,所以我以这种方式隐藏它们,但我遇到了问题,例如鼠标位置可能是通过执行 Browser.Mouse.PositionOnDocument = new Point(123,123) 设置,我希望该功能仅在内部(命名空间级别)可用,而不是对库的最终用户可用。

应该允许例如,另一个模块想要改变鼠标的位置,它可以正常改变它,只需访问 Browser.Mouse.PositionOnDocument = new Point();

但这应该只允许来自同一个命名空间,不能来自外部使用,外部使用应该是只读的。

using System;
using System.Drawing;

namespace Test
{
    using InternalNamespace;

    class Program
    {
        public static void Main()
        {
            Browser browser = new Browser();
            browser.Initialize();

            Console.WriteLine(browser.Mouse.PositionOnDocument);

            // This should NOT be allowed because its being used from outside the Browser namespace
            browser.Mouse.PositionOnDocument = new Point(2, 2);

            Console.WriteLine(browser.Mouse.PositionOnDocument);

        }
    }
}

namespace InternalNamespace
{
    public class Browser
    {
        public BrowserMouse Mouse;

        public void Initialize()
        {
            // Instantiate the main mouse with underlying specific puppeteer module
            // pass a null module to the specific module because it doesnt need 
            // one underlying specific module
            Mouse = new BrowserMouse(this, new BrowserPuppeteerChromiumSpecificMouse(this, null));

            // This is OK because its inside the namespace
            Mouse.PositionOnDocument = new Point(1, 1);
        }
    }

    public class BrowserModule
    {
        // ExternalBrowser that owns this module.
        protected Browser Browser { get; set; }

        // Underlying specific module => Puppeteer, Selenium or anything.
        protected BrowserModule SpecificModule { get; set; }

        public BrowserModule(Browser browser, BrowserModule specificModule)
        {
            Browser = browser;

            if (specificModule != null)
                SpecificModule = specificModule;
        }
    }

    interface ILocatable
    {
        Point PositionOnDocument { get; set; }
    }

    // This whole class just servers as a proxy for the underlying module.  
    public class BrowserMouse : BrowserModule, ILocatable
    {
        public BrowserMouse(Browser browser, BrowserModule specificModule) : base(browser, specificModule)
        {
        }

        // This delegates the call to the specific module.
        public Point PositionOnDocument
        {
            get
            {
                return ((ILocatable)SpecificModule).PositionOnDocument;
            }
            set
            {
                ((ILocatable)SpecificModule).PositionOnDocument = value;
            }
        }
    }

    // The specific module implementation that is gonna be called from the main module.
    public class BrowserPuppeteerChromiumSpecificMouse : BrowserModule, ILocatable
    {
        public BrowserPuppeteerChromiumSpecificMouse(Browser browser, BrowserModule specificModule) : base(browser, specificModule)
        {
        }

        public Point PositionOnDocument { get; set; }
    }
}

【问题讨论】:

  • 你控制界面吗?
  • V0ldek 之所以问这个问题是因为如果您控制界面,您可以删除属性的set; 部分使其成为只读。
  • 是的,我愿意。但是,如果我删除了 setter,我需要在该接口的每个实现者中创建一个支持字段,对吗?
  • @Joso Vitor 不,从 C#6 开始支持仅获取自动属性
  • @V0ldek 好吧,实现者特定的类将需要改变这个位置,如果属性是 get 只有我需要一个由这些特定类设置的字段

标签: c# module interface polymorphism public


【解决方案1】:

此接口允许实现者公开PositionOnDocument

并非如此,接口要求实现者履行合同,合同是有一个属性PositionOnDocument 具有公共getter 和setter。如果你不能改变接口,你就不能在没有 setter 的情况下实现它——你不能违反合同。

如果可以更改接口,请从合约中删除 setter。

interface IExternalBrowserLocatable
{
    Point PositionOnDocument { get; }
} 

如果您希望 setter 可在内部访问,请将其设为可见性 internal

internal class ExternalBrowserPuppeteerChromiumSpecificMouse : ExternalBrowserPuppeteerChromiumSpecificModule, IExternalBrowserLocatable
{
    ... 
    public Point PositionOnDocument { get; internal set; }
}

编辑:

如果您想要一个仅在内部可用的 setter 通用接口,您可以这样做:

// You can probably come up with a better name.
internal interface IExternalBrowserLocatableMutable : IExternalBrowserLocatable
{
    new Point PositionOnDocument { get; set; }
}

internal class ExternalBrowserPuppeteerChromiumSpecificMouse : ExternalBrowserPuppeteerChromiumSpecificModule, IExternalBrowserLocatableMutable
{
    ... 
    public Point PositionOnDocument { get; set; }
}

public 类实现internal 接口是完全合法的(并且非常有用!),您可以将实现类转换为直接的internal 接口或其public 基类。通过internal 接口(或类本身)访问时,您将同时获得getter 和setter。 public 接口只暴露了 getter。

【讨论】:

  • 好的,我以前试过,但问题是(TLDR)所有这些“模块”(鼠标、键盘等)都将派生自 ExternalBrowserModule,其中包含对 ExternalBrowser 的引用拥有它。我需要每个模块都可以访问其中的其他模块。举个例子,ExternalBrowserScroller 需要设置鼠标的位置,因为当您使用鼠标滚轮滚动浏览器窗口时,鼠标指针会跟随它......并且如果没有接口成员上的设置器,我无​​法从主要类,因为只有特定的类使用“set”
  • 我可以在特定的滚动条中进行操作,例如 ExternalBrowserPuppeteerChromiumSpecificScroller, "ExternalBrowser.Mouse.PositionOnDocument = x",但这是不可能的,因为只有底层的 SpecificModule 使用内部设置器,"Main" 类可以不,请记住有两个“鼠标”类,一个触发 SpecificModule 上的方法和 SpecificModule 本身。
  • @JoaoVitor 所以你想让你的类共享一个公开internal setter 的公共接口,但是以一种不能从外部访问它的方式?
  • 已编辑以支持上述 ^
  • 那么ExternalBrowserMouse应该实现IExternalBrowserLocatable,而ExternalBrowserPuppeteerChromiumSpecificMouse应该实现IExternalBrowserLocatableMutable?在实现可变的时,它既显式地实现又隐式地实现是正常的吗?
【解决方案2】:

首先,internal 访问不允许从程序集外部访问,而不是从不同的命名空间访问。在同一个程序集中从完全不同的命名空间引用internal 成员是完全合法的,即使在同一个命名空间中从不同的程序集引用也是非法的。我假设您想限制 .NET internal 意义上的访问,因为您描述的特定于命名空间的行为是不可能的。

您的ILocatable 界面很肤浅。它只是为了让您可以在内部进行演员(ILocatable)SpecificModule,并且该演员可能是灾难性的,因为BrowserModule不一定实现ILocatable,所以它是一个可能的InvalidCastException,这不应该在正确的代码中发生.

既然您无论如何都要向公众公开BrowserModule,并且您显然希望任何BrowserModule 具有PositionOnDocument 属性,请将其转换为具有abstract 属性的abstract 类和@987654334 @二传手:

public abstract class BrowserModule
{
    // ExternalBrowser that owns this module.
    protected Browser Browser { get; set; }

    // Underlying specific module => Puppeteer, Selenium or anything.
    protected BrowserModule SpecificModule { get; set; }

    public abstract Point PositionOnDocument { get; internal set; }

    ...
}

public class BrowserMouse : BrowserModule
{
    ...

    // This delegates the call to the specific module.
    public override Point PositionOnDocument
    {
        get
        {
            return SpecificModule.PositionOnDocument;
        }
        internal set
        {
            SpecificModule.PositionOnDocument = value;
        }
    }
}

// The specific module implementation that is gonna be called from the main module.
public class BrowserPuppeteerChromiumSpecificMouse : BrowserModule
{
    ...

    public override Point PositionOnDocument { get; internal set; }
}

【讨论】:

  • 谢谢,我尝试过使用抽象类和不使用抽象类,但我注意到我需要在浏览器中需要找到的所有模块或项目上实现 PositionOnDocument,可能我给人的印象是所有模块都应该有一个 PositionOnDocument,这是不正确的,Mouse 和 HtmlElement 是需要该位置的类,例如,允许浏览器访问页面的“Navigator”不需要它,所以它是有意义的BrowserModule 抽象,从中派生 BrowserMouse 并放置要覆盖的 PositionOnDocument...
  • @JoaoVitor 那为什么BrowserMouse 中的代码假定任何模块都必须实现ILocatable?这始于一个特定的技术性“在不公开公开属性设置器的情况下实现接口?”问题,可以 - 并且已经 - 得到了合理的回答,但现在看来你应该把你的设计带回绘图板上,并提出一个更好的层次结构。我建议你这样做,如果在这样做的过程中你发现了一个可以用 SO 问题表述的特定问题,请再问一个。
猜你喜欢
  • 2018-02-27
  • 2014-04-07
  • 2014-11-14
  • 2014-01-07
  • 2011-05-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-10-20
相关资源
最近更新 更多