我可以理解为什么有 public 和 private 访问修饰符,这两种语言几乎都可以找到。我甚至可以理解为什么可能存在包修饰符,因为您可能希望让您的类(那些紧密相关的类)以某种方式相互交互,这不适合公共交互(例如,因为它取决于类内部的知识,可能是因为它会揭示一个秘密,或者可能因为它可能随时改变并依赖它会破坏所有现有代码,等等)。但是,为什么我想要一个受保护的标识符?不要误解我的意思,我知道 protected 意味着什么,但为什么我希望我的类的子类访问某些实例变量或使用某些方法,因为它们是子类,即使它们是它们的一部分一个不同的包? protected 的真实用例是什么?
(作为实例变量的参数的性能不计,因为JIT编译器总是可以内联访问器方法,将其调用开销减少到零)
答案 0 :(得分:15)
公共方法是公共接口的一部分。私有方法是内部的。受保护的方法是扩展点。
使用protected
,您可以通过覆盖它来重新定义类的功能,而无需将此方法作为公共接口的一部分。
另一件事 - 受保护的方法是可以由子类重用的常用方法,但同样不需要成为公共接口的一部分。
例如,在java集合框架中,有AbstractList
类。它已保护modCount
字段和受保护的removeRange
方法:
所有子类使用(递增)modCount
字段来计算修改次数。 Iterator
返回的AbstractList
使用该字段
removeRange
方法可以由子类重用,而不是让它们再次定义。
正如评论中所述,以及布洛赫的演讲 - 记录你的课程。如果它是为了继承 - 做出额外的努力。
答案 1 :(得分:3)
我看到的最频繁的用法实际上是让超类使用子类的内部。考虑一下:
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.)
答案 2 :(得分:0)
当你不完全了解可能的扩展器的思想时,我认为它们是一种捷径。假设您的基类有5或6个属性。您当然不希望这些属性(或其设置方法)公开。但是你可以看到扩展器可能想要编写可以改变这些属性值的函数。所以你让他们(或更好,他们的集合)受到保护。对于扩展器来说,总会有依赖于设计的设计,但对于任何旧的消费代码都不行。
那就是说,我告诉我的学生“受保护是待办事项清单”。因为如果你改变任何受保护的东西,你必须去寻找依赖它的人。因此,在将其暴露给未知的未知扩展者之前,请务必确定一些事情。
答案 3 :(得分:0)
存在没有protected
访问修饰符的语言,甚至没有访问修饰符的语言 所有方法都是公共的和< / em>这些语言通常被认为是一些“最纯粹”的OO语言,事实证明,不“真的需要”protected
访问修饰符。< / p>
答案 4 :(得分:0)
公共可继承类中的公共成员构成与整个世界的契约,该契约对所有行为良好的子类具有约束力。受保护的成员构成与任何直接子类的合同,该子类仅对暴露它的类具有约束力。子类没有义务将任何受保护的成员暴露给它们自己的子类(实际上,应该有一个拒绝这种暴露的约定,以及一种指定特定受保护成员默认只能用于直接子类的方法,除非那些子类指定它也应该可用于它们的子类。
有些人可能会认为某个类不允许访问其父母所暴露的受保护成员,但这种行为绝不违反Liskov替代原则。 LSP声明如果代码在期望基类型对象时可能会接收派生类型对象,则派生类型对象应该能够执行基类型对象可以执行的任何操作。因此,如果Moe
公开支持某个功能,而Larry
派生的类型Moe
没有,那么接受Moe
类型参数的代码会尝试使用该代码如果给出Larry
,则功能将失败;这种失败将构成违反LSP的行为。
但是,假设Moe
包含protected
不支持的Larry
功能。允许使用该功能的唯一类可以是直接派生自Moe
的类,也可以是支持它的Moe
后代。如果Curly
派生自Moe
,则可以使用该功能,而无需担心Moe
的所有子类型是否都支持它,因为Curly
的基数不会是某些任意对象,它是Moe
的实例或衍生物(可能支持或可能不支持该功能) - 它将是Moe
,句点。
如果派生类型以破坏使用它们的基础的公共成员的方式使用这些字段,则受保护字段可能会引入一些与LSP相关的问题。另一方面,不需要使用受保护的变量。如果子类型以与基类型的预期行为不同的方式实现虚拟成员,则可能会破坏基类型的公共成员,即使不触及任何基类保护成员。
答案 5 :(得分:0)
当我想要a)消除公共合同中的噪声时,我个人利用受保护的修饰符b)同时与派生类共享功能,同时c)编写DRY代码并且还注意单一责任原则。听起来很典型,我确定,但让我举一个例子。
这里我们有一个基本的查询处理程序接口:
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);
答案 6 :(得分:-1)
可能有一些信息/财产,您将与您的孩子分享,即使他们已婚并住在另一个国家。顺便说一句,有些语言没有protected
修饰符。