为什么面向对象语言真的需要 PROTECTED 访问修饰符?

发布于 2024-10-04 14:44:23 字数 416 浏览 7 评论 0原文

我可以理解为什么有 publicprivate 访问修饰符,这两个修饰符几乎在任何语言中都可以找到。我什至可以理解为什么可能有一个 package 修饰符,因为您可能希望让您的类(紧密属于在一起的类)以不适合公共交互的方式彼此交互(例如,因为它取决于类内部的知识,也许因为它会泄露一个秘密,或者可能因为它可能随时更改并且依赖它会破坏所有现有代码,等等)。但是,为什么我想要一个受保护的标识符?不要误会我的意思,我知道 protected 的含义,但是为什么我希望我的类的子类访问某些实例变量或使用某些方法,仅仅因为它们是子类,即使它们是子类的一部分不同的包? protected 的真实用例是什么?

(并且性能作为实例变量的参数并不重要,因为 JIT 编译器始终可以内联访问器方法,将其调用开销减少到零)

I can understand why there is public and private access modifier, these two are also found in almost any language. I can even understand why there might be a package modifier, as you may want to have your classes (those that tightly belong together) interact with each other in a way, that is not suitable for public interaction (e.g. as it depends upon knowledge of class internals, maybe because it would reveal a secret, or maybe because it may change at any time and relying on it will break all existing code, and so on). However, why would I want to have a protected identifier? Don't get me wrong, I know what protected means, but why would I want subclasses of my classes to access certain instance variables or use certain methods, just because they are subclasses and even if they are part of a different package? What is a real world use case for protected?

(And performance as an argument for instance variables does not count, since a JIT compiler can always inline accessor methods, decreasing their call overhead to zero)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

捎一片雪花 2024-10-11 14:44:23

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

使用protected,您可以通过覆盖类来重新定义类的功能,而无需将此方法作为公共接口的一部分。

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

例如,在java集合框架中,有AbstractList类。它具有受保护的 modCount 字段和受保护的 removeRange 方法:

  • 所有子类都使用(递增)modCount 字段来计算数量的修改。 AbstractList 返回的 Iterator 使用该字段

  • 子类可以重用 removeRange 方法,而不是让它们再次定义它.

查看 Josh Bloch 关于 API 设计的相关演示。

正如评论中和布洛赫的演示中所述 - 很好地记录你的课程。如果它是为了继承 - 付出额外的努力。

Public methods are part of the public interface. Private methods are internals. Protected methods are points of extension.

With protected you can redefine the functioning of a class by overriding it without making this method part of the public interface.

Another thing - protected methods are common methods that can be reused by subclasses, but again don't need to be part of the public interface.

For example, in the java collection framework, there is the the AbstractList class. It has protected modCount field and a protected removeRange method:

  • the modCount field is used (incremented) by all subclasses to count the number of modifications. The Iterator returned by AbstractList makes use of that field

  • the removeRange method can be reused by subclasses, instead of having them define it again.

See this related presentation by Josh Bloch on API design.

As noted in the comments, and in the presentation by Bloch - document your class well. And if it is meant for inheritance - make extra effort.

岁吢 2024-10-11 14:44:23

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

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 修饰符。这称为模板方法模式,并且经常使用。 (很抱歉没有提供真实的示例。如果您需要一些,请前往维基百科。

The most frequent use I see is actually to let a superclass use a subclass's internals. Consider this:

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

In this case, it's Foo that needs to use the protected element of Bar, and not the opposite. If you want your superclass to access the logic of a subclass, but don't want it to be publicly exposed, you have no choice but the protected modifier. This is called the template method pattern, and it's often used. (Sorry for not providing a real-world example. Head to Wikipedia if you want some.)

小矜持 2024-10-11 14:44:23

当你不完全了解可能的扩展者的想法时,我将它们视为一条捷径。假设您的基类有 5 或 6 个属性。您当然不希望这些属性(或其设置方法)公开。但您可以看到扩展程序可能想要编写可以更改这些属性值的函数。所以你要保护它们(或者更好的是它们的集合)。总是存在对设计的依赖,这对于扩展器来说可能是可以的,但对于任何旧的消费代码来说是不行的。

也就是说,我告诉我的学生“受保护是一个待办事项列表”。因为如果你改变任何受保护的东西,你必须去寻找谁依赖它。因此,在将某些东西暴露给未来未知的扩展者之前,请务必确定它。

I see them as a shortcut when you don't fully know the mind of possible extenders. Say your base class has 5 or 6 attributes. You certainly don't want those attributes (or their set methods) public. But you can see that extenders might want to write functions that would change the values of those attributes. So you make them (or better, their sets) protected. There will always be reliance-on-design that might be ok for an extender but isn't ok for just any old consuming code.

That said, I tell my students "protected is a to-do list". Because if you change anything protected, you have to go looking to find who relied on it. So be really sure about something before you expose it to the unknown extenders of the future.

雾里花 2024-10-11 14:44:23

事实上,存在没有 protected 访问修饰符的语言,甚至根本没有访问修饰符的语言,其中所有方法都是公共的,和< /em> 这些语言通常被认为是一些“最纯粹的” OO 语言,表明您实际上“并不真正需要”受保护的访问修饰符。

The fact that languages exist which do not have a protected access modifier, and even languages that do not have access modifiers at all where all methods are public, and those languages are generally considered to be some of the "most pure" OO languages, shows that you do, in fact, not "really need" a protected access modifier.

吹梦到西洲 2024-10-11 14:44:23

公共可继承类中的公共成员构成了与整个世界的契约,对所有行为良好的子类都有约束力。受保护的成员与任何直接子类构成契约,该契约仅对公开它的类具有约束力。子类没有义务向它们自己的子类公开任何受保护的成员(实际上,应该有一个拒绝此类公开的约定,以及指定特定受保护成员默认情况下仅可用于直接子类的方法,除非这些成员子类指定它也应该可供其子类使用)。

有些人可能会对类不允许访问其父类公开的受保护成员的想法感到恼火,但这种行为无论如何都不会违反里氏替换原则。 LSP 规定,如果代码在需要基类型对象时可以接收派生类型对象,则派生类型对象应该能够执行基类型对象可以执行的任何操作。因此,如果 Moe 公开支持某个功能和类型 Larry,而从 Moe 派生的 Larry 则不支持,则接受 Larry 类型参数的代码code>Moe 并尝试使用该功能,如果给它一个 Larry,就会失败;此类失败将构成对 LSP 的违反。

但是,假设 Moe 包含 Larry 不支持的受保护 功能。唯一允许使用该功能的类要么是直接派生自 Moe 的类,要么是来自支持该功能的 Moe 后代的类。如果 Curly 派生自 Moe,它可以使用该功能,而不必担心 Moe 的所有子类型是否都支持它,因为 Curly 的基础不会是任何 Moe 实例或派生对象(并且可能支持也可能不支持该功能)的任意对象 - 它将是一个 Moe,句号。

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

A public member in a public inheritable class constitutes a contract with the entire world which is binding upon all well-behaved subclasses. A protected member constitutes a contract with any immediate subclasses, which is only binding upon the class which is exposes it. Subclasses are under no obligation to expose any protected members to their own subclasses (indeed, there should be a convention for denying such exposure, as well as a means of specifying that a particular protected member should by default only be available to immediate subclasses unless those subclasses specify that it should be available to their subclasses as well).

Some people may chafe at the idea of a class not allowing access to protected members exposed by its parent, but such behavior is not in any way a violation of the Liskov Substitution Principle. The LSP states that if code may receive a derived-type object when it expects a base-type object, the derived-type object should be able to do anything the base-type object can. Thus, if Moe publicly supports a feature and a type Larry, which derives from Moe doesn't, code which accepts a parameter of type Moe and tries to use that feature would fail if it was given a Larry; such failure would constitute a violation of the LSP.

Suppose, however, that Moe includes a protected feature which Larry does not support. The only classes that would be allowed to use that feature would be either those that directly derive from Moe, or from descendants of Moe that support it. If Curly derives from Moe, it can use that feature without having to worry about whether all subtypes of Moe support it, because Curly's base won't be some arbitrary object that's either an instance of Moe or a derivative (and might or might not support the feature)--it will be a Moe, period.

It is possible for protected fields to introduce some LSP-related issues if derived types use those fields in a manner which breaks public members of the base which use them. On the other hand, one needn't use protected variables for that. If a subtype implements virtual members in a way which differs from the expected behavior of the base type, that may break public members of the base type, even without touching any base-class protected members.

傲影 2024-10-11 14:44:23

当我想要 a) 消除公共契约中的噪音,同时 b) 还与派生类共享功能,同时 c) 编写 DRY 代码并且同时注意单一职责原则时,我个人会使用 protected 修饰符。我确信这听起来很典型,但让我举一个例子。

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

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 方法。它也不关心查询是如何实现的。 protected 修饰符非常适合这样的场景。

另外,我不想在每个查询处理程序中重复相同的样板类型的代码,特别是因为如果缓存管理器接口发生更改,那么重构将是一场噩梦,如果此时完全删除缓存,那么重构将是一场噩梦等级。

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

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

I personally utilize the protected modifier when I want to a) eliminate noise in the public contract yet b) also share functionality with derived classes while in-turn c) writing code that is DRY and it also mindful of the single responsibility principle. Sounds typical I'm sure but let me provide an example.

Here we have a basic query handler interface:

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

This interface is implemented by many different query handlers in the application. Let's say then later that I have a need to cache the query results from many different query handlers. This is really just an implementation detail though. The consumers of any concrete query handler class aren't, generally speaking, concerned about that. My solution then is to create an implementation that takes care of the caching but defers the actual querying responsibility to any derived classes.

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

The CachedQueryHandler doesn't intend for anyone else to call the ExecuteCore method directly. It also doesn't care how the querying is implemented though. The protected modifier is perfect for a scenario like this.

Also, I don't want to be repeating that same boiler plate type of code in every query handler particularly because it would be a nightmare to refactor if the cache manger interface changed and a real pain to take out if caching was removed completely at this level.

Here is what a concrete widget query handler would look like:

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

Now, if the consumers of the widget query handler utilize it via the query handler interface, which if I utilize dependency injection I can certainly enforce, they will never use anything specific to caching that is added to the CachedQueryProvider class. I'm then free to add/remove caching as necessary or change the caching implementation entirely even with a minimal amount of effort.

IQueryHandler<WidgetCommand, Widget> widgetQueryHandler;
var widgets = widgetQueryHandler.Execute(myWidgetCommand);
々眼睛长脚气 2024-10-11 14:44:23

也许,您会与您的孩子分享一些信息/财产,即使他们已婚并居住在另一个国家。顺便说一句,有些语言没有 protected 修饰符。

Probably, there is some information/property which you will share with your children, even if they are married and live in another country. BTW, there are languages which don't have the protected modifier.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文