代码之家  ›  专栏  ›  技术社区  ›  Mecki

为什么OO语言真的需要一个受保护的访问修饰符?

  •  4
  • Mecki  · 技术社区  · 13 年前

    我能理解为什么 公众的 私有的 访问修饰符,这两个也可以在几乎任何语言中找到。我甚至能理解为什么 包裹 修饰符,因为你可能想让你的类(那些紧密地属于在一起的)以某种方式相互作用,这是不适合于公共交互的(例如,它依赖于类内部结构的知识),可能是因为它会泄露秘密,或者可能因为它随时可能改变,并且依赖于它会破坏所有现有的代码,等等)。但是,我为什么要 受保护的 标识符?别误会我,我知道 受保护的 意思是,但为什么我希望类的子类访问某些实例变量或使用某些方法,仅仅因为它们是子类,即使它们是不同包的一部分?什么是真实世界的用例 受保护的 ?

    (而性能作为实例变量的参数不算在内,因为JIT编译器总是可以内联访问器方法,从而将它们的调用开销减少到零)

    7 回复  |  直到 13 年前
        1
  •  15
  •   Bozho Michał Mech    13 年前

    公共方法是公共接口的一部分。私有方法是内部的。受保护的方法是扩展点。

    protected 可以通过重写类来重新定义类的功能,而不必使此方法成为公共接口的一部分。

    另一个受事物保护的方法是可以被子类重用的公共方法,但同样不需要成为公共接口的一部分。

    例如,在java集合框架中,有 AbstractList 上课。它已经保护了 modCount 字段和受保护的 removeRange 方法:

    • 这个 模式计数 字段被所有子类使用(递增)来计算修改次数。这个 Iterator 返回者 摘要列表 利用那个领域

    • 这个 移除范围 方法可以由子类重用,而不是让它们再次定义它。

    See this Josh Bloch关于API设计的相关演示。

    正如评论和布洛赫的演讲中所指出的,好好记录你的课程。如果这是为了继承-要付出额外的努力。

        2
  •  3
  •   zneak    13 年前

    我看到的最常见的用法实际上是让超类使用子类的内部。考虑一下:

    class Foo
    {
        private int[] array = new int[] { 4, 3, 2, 1 };
    
        public void processAllElements()
        {
            for (int i = 0; i < array.length; i++)
                processElement(array[i]);
        }
    
        protected abstract void processElement(int i);
    }
    
    class Bar
    {
        protected void processElement(int element)
        {
            System.out.println(element);
        }
    }
    

    在这种情况下 Foo 需要使用 Bar ,而不是相反。如果您希望您的超类访问子类的逻辑,但不希望它公开,那么除了 protected 修饰语。这称为模板方法模式,并且经常使用。(很抱歉没有提供一个真实的例子。 Head to Wikipedia if you want some. )

        3
  •  0
  •   Kate Gregory duffymo    13 年前

    当你不完全了解可能的扩展器时,我把它们看作是一条捷径。假设你的基类有5或6个属性。您当然不希望这些属性(或它们的set方法)公开。但是您可以看到,扩展程序可能希望编写可以更改这些属性值的函数。所以你要保护他们(或者更好的,他们的设备)。对于扩展器来说,设计可能是可以的,但对于任何旧的使用代码来说都是不可以的。

    也就是说,我告诉我的学生“受保护是一个待办事项清单”。因为如果你改变任何受保护的东西,你必须去寻找谁依赖它。因此,在将某些内容公开给未来未知的扩展器之前,一定要确定它。

        4
  •  0
  •   Jörg W Mittag    13 年前

    语言不存在的事实 protected 访问修饰符,甚至没有访问修饰符的语言 完全 所有方法都是公开的, 这些语言通常被认为是一些“最纯”的面向对象语言,这表明实际上, “真的需要”a 受保护的 访问修饰符。

        5
  •  0
  •   supercat    11 年前

    公共可继承类中的公共成员构成与整个世界的契约,该契约约束所有表现良好的子类。受保护的成员与任何直接子类构成一个协定,该协定仅对公开它的类具有约束力。子类没有义务将任何受保护成员暴露在其自己的子类之下(事实上,应该有一个公约来拒绝这种暴露,以及一种指定特定受保护成员默认情况下只能对直接子类可用的方法,除非这些子类指定它也应该对其子类可用)。

    有些人可能会对某个类不允许访问其父类公开的受保护成员的想法感到恼火,但这种行为绝不违反Liskov替换原则。LSP声明,如果代码可能在需要基类型对象时接收派生类型对象,则派生类型对象应该能够执行基类型对象可以执行的任何操作。因此,如果 Moe 公开支持功能和类型 Larry ,来源于 教育部 不,接受类型为的参数的代码 教育部 如果给它一个 拉里 ;此类失败将构成对LSP的违反。

    然而,假设 教育部 包括 protected 特征 拉里 不支持。唯一允许使用该特性的类是那些直接从 教育部 ,或来自 教育部 支持它。如果 Curly 源自 教育部 ,它可以使用该功能,而不必担心 教育部 支持它,因为 卷曲的 的基不会是任意对象,也不会是 教育部 或者派生(可能支持也可能不支持该特性)--它将是 教育部 ,期间。

    如果派生类型以破坏使用这些字段的基的公共成员的方式使用这些字段,则受保护字段可能会引入一些与LSP相关的问题。另一方面,不必使用受保护的变量。如果子类型以不同于基类型的预期行为的方式实现虚拟成员,则可能会断开基类型的公共成员,即使不触及任何受基类保护的成员。

        6
  •  0
  •   Sacrilege    8 年前

    当我想a)消除公共契约中的噪音,b)也与派生类共享功能时,我个人使用受保护的修饰符,而c)编写枯燥的代码时,它也注意到单一责任原则。听起来很典型,但让我举个例子。

    这里有一个基本的查询处理程序接口:

    public interface IQueryHandler<TCommand, TEntity>
    {
        IEnumerable<TEntity> Execute(TCommand command);
    }
    

    此接口由应用程序中的许多不同查询处理程序实现。我们稍后再说,我需要缓存来自许多不同查询处理程序的查询结果。不过,这实际上只是一个实现细节。一般来说,任何具体查询处理程序类的使用者都不关心这个问题。然后,我的解决方案是创建一个负责缓存的实现,但将实际的查询责任推给任何派生类。

    public abstract class CachedQueryHandler<TCommand, TEntity> 
        : IQueryHandler<TCommand, TEntity>
    {
        public IEnumerable<TEntity> Execute(TCommand command)
        {
            IEnumerable<TEntity> resultSet = this.CacheManager
                .GetCachedResults<TEntity>(command);
    
            if (resultSet != null)
                return resultSet;
    
            resultSet = this.ExecuteCore(command);
            this.CacheManager.SaveResultSet(command, resultSet);
    
            return resultSet;
        }
    
        protected abstract IEnumerable<TEntity> ExecuteCore(TCommand command);
    }
    

    CachedQueryHandler不打算让其他任何人直接调用ExecuteCore方法。它也不关心查询是如何实现的。受保护的修饰符非常适合这样的场景。

    另外,我不想在每个查询处理程序中重复相同的锅炉盘类型的代码,特别是因为如果缓存管理器接口发生更改,重构将是一场噩梦,如果在这个级别上完全删除了缓存,重构将是一场真正的痛苦。

    下面是具体的小部件查询处理程序的外观:

    public class DatabaseWidgetQueryHandler : CachedQueryHandler<WidgetCommand, Widget>
    {
        protected override IEnumerable<Widget> ExecuteCore(WidgetCommand command)
        {
            return this.GetWidgetsFromDatabase();
        }
    }
    

    现在,如果小部件查询处理程序的使用者通过查询处理程序接口使用它(如果我使用依赖注入,我肯定可以强制使用),那么他们将永远不会使用添加到CachedQueryProvider类的缓存特定的任何东西。然后,我可以根据需要自由添加/删除缓存,或者完全更改缓存实现,即使只需很少的努力。

    IQueryHandler<WidgetCommand, Widget> widgetQueryHandler;
    var widgets = widgetQueryHandler.Execute(myWidgetCommand);
    
        7
  •  -1
  •   khachik    13 年前

    也许,你会与你的孩子分享一些信息/财产,即使他们已经结婚并生活在另一个国家。顺便说一下,有些语言没有 protected 修饰语。