什么是public、private和protected的区别?

1137

在类中,何时以及为什么要使用publicprivateprotected函数和变量?它们之间有什么区别?

示例:

// Public
public $variable;
public function doSomething() {
  // ...
}

// Private
private $variable;
private function doSomething() {
  // ...
}

// Protected
protected $variable;
protected function doSomething() {
  // ...
}

69
我认为,提供每个关键词的字面含义并不足以解决问题,这个问题也需要提供实际应用示例作为答案,以使回答更加生动易懂。 - Matthew
16个回答

1422

在IT技术中,你可以使用以下作用域(scope):

  • 使用public作用域可使该属性/方法在任何地方都可以访问,包括其他类和对象实例。

  • 使用private作用域可使该属性/方法仅在其自身类中可见。

  • 使用protected作用域可使该属性/方法在继承当前类的所有类(包括父类)中可见。

如果您没有使用任何可见性修饰符,则该属性/方法将默认为public。

更多信息: (详细信息请参阅)


89
当您希望使您的变量/函数在扩展当前类及其父类的所有类中可见时,请使用“protected”范围。 - Shahid
4
@Shahid - 我不明白你的观点。任何继承类A的类也继承了A的父类,对吗? - JDelage
4
@JDelage - 请查看链接http://www.php.net/manual/zh/language.oop5.visibility.php#109324 - Shahid
5
为什么还要使用对象呢? - J.Steve
31
一个更有帮助的回答是尽可能隐藏对象的内部工作原理,这样它就不太可能出错。如果你将所有内容都公开,那么另一个程序员可能会更改某个变量,而你不希望它被除对象内部工作之外的任何东西改变。 - Relaxing In Cyprus
显示剩余4条评论

1343

dd

公开:

当你将一个方法(函数)或属性(变量)声明为公开时,这些方法和属性可以被以下方式访问:

  • 声明它的同一个类。
  • 继承上述声明类的类。
  • 任何在该类之外的外部元素也可以访问这些内容。

示例:

<?php

class GrandPa
{
    public $name='Mark Henry';  // A public variable
}

class Daddy extends GrandPa // Inherited class
{
    function displayGrandPaName()
    {
        return $this->name; // The public variable will be available to the inherited class
    }

}

// Inherited class Daddy wants to know Grandpas Name
$daddy = new Daddy;
echo $daddy->displayGrandPaName(); // Prints 'Mark Henry'

// Public variables can also be accessed outside of the class!
$outsiderWantstoKnowGrandpasName = new GrandPa;
echo $outsiderWantstoKnowGrandpasName->name; // Prints 'Mark Henry'

受保护的:

当你将一个方法(函数)或属性(变量)声明为protected时,这些方法和属性可以被以下方式访问:

  • 声明它的同一个类。
  • 继承了上述声明类的类。

外部成员无法访问这些变量。所谓的“外部成员”是指它们不是声明类本身的对象实例。

示例:

<?php

class GrandPa
{
    protected $name = 'Mark Henry';
}

class Daddy extends GrandPa
{
    function displayGrandPaName()
    {
        return $this->name;
    }

}

$daddy = new Daddy;
echo $daddy->displayGrandPaName(); // Prints 'Mark Henry'

$outsiderWantstoKnowGrandpasName = new GrandPa;
echo $outsiderWantstoKnowGrandpasName->name; // Results in a Fatal Error

具体的错误将是这样的:
PHP致命错误:无法访问受保护的属性GrandPa::$name

私有:

当你将一个方法(函数)或属性(变量)声明为私有时,这些方法和属性只能被以下对象访问:

  • 声明它的同一个类。

外部成员无法访问这些变量。外部成员指的是不是声明类的对象实例,甚至是继承了声明类的类。

示例:

<?php

class GrandPa
{
    private $name = 'Mark Henry';
}

class Daddy extends GrandPa
{
    function displayGrandPaName()
    {
        return $this->name;
    }

}

$daddy = new Daddy;
echo $daddy->displayGrandPaName(); // Results in a Notice 

$outsiderWantstoKnowGrandpasName = new GrandPa;
echo $outsiderWantstoKnowGrandpasName->name; // Results in a Fatal Error

具体的错误信息如下:
通知:未定义属性:Daddy::$name 致命错误:无法访问私有属性:GrandPa::$name

使用反射解剖Grandpa类

这个主题并不是超出范围的,我在这里加上它只是为了证明反射的强大。正如我在上面的三个例子中所述,protectedprivate成员(属性和方法)不能在类外部访问。

然而,通过反射,你甚至可以在类外部访问protectedprivate成员,这是非常了不起的!

那么,什么是反射?

反射增加了逆向工程类、接口、函数、方法和扩展的能力。此外,它们还提供了获取函数、类和方法的文档注释的方法。

前言

我们有一个名为Grandpas的类,假设我们有三个属性。为了便于理解,假设有三个名字为:

  • Mark Henry
  • John Clash
  • Will Jones
让我们分别将它们(修饰符)设置为publicprotectedprivate。你很清楚protectedprivate成员无法在类外部访问。现在让我们使用反射来反驳这个说法。

代码

<?php

class GrandPas   // The Grandfather's class
{
    public     $name1 = 'Mark Henry';  // This grandpa is mapped to a public modifier
    protected  $name2 = 'John Clash';  // This grandpa is mapped to a protected  modifier
    private    $name3 = 'Will Jones';  // This grandpa is mapped to a private modifier
}


# Scenario 1: without reflection
$granpaWithoutReflection = new GrandPas;

# Normal looping to print all the members of this class
echo "#Scenario 1: Without reflection<br>";
echo "Printing members the usual way.. (without reflection)<br>";
foreach($granpaWithoutReflection as $k=>$v)
{
    echo "The name of grandpa is $v and he resides in the variable $k<br>";
}

echo "<br>";

#Scenario 2: Using reflection

$granpa = new ReflectionClass('GrandPas'); // Pass the Grandpas class as the input for the Reflection class
$granpaNames=$granpa->getDefaultProperties(); // Gets all the properties of the Grandpas class (Even though it is a protected or private)


echo "#Scenario 2: With reflection<br>";
echo "Printing members the 'reflect' way..<br>";

foreach($granpaNames as $k=>$v)
{
    echo "The name of grandpa is $v and he resides in the variable $k<br>";
}

输出:

#Scenario 1: Without reflection
Printing members the usual way.. (Without reflection)
The name of grandpa is Mark Henry and he resides in the variable name1

#Scenario 2: With reflection
Printing members the 'reflect' way..
The name of grandpa is Mark Henry and he resides in the variable name1
The name of grandpa is John Clash and he resides in the variable name2
The name of grandpa is Will Jones and he resides in the variable name3

常见误解:
请不要将下面的示例与之混淆。正如您所看到的,私有(private)和受保护(protected)成员在没有使用反射的情况下无法在类外部访问。
<?php

class GrandPas   // The Grandfather's class
{
    public     $name1 = 'Mark Henry';  // This grandpa is mapped to a public modifier
    protected  $name2 = 'John Clash';  // This grandpa is mapped to a protected modifier
    private    $name3 = 'Will Jones';  // This grandpa is mapped to a private modifier
}

$granpaWithoutReflections = new GrandPas;
print_r($granpaWithoutReflections);

输出:

GrandPas Object
(
    [name1] => Mark Henry
    [name2:protected] => John Clash
    [name3:GrandPas:private] => Will Jones
)

调试函数

print_rvar_exportvar_dump调试器函数。它们以人类可读的形式呈现有关变量的信息。这三个函数将显示PHP 5中对象的protectedprivate属性。静态类成员将不会被显示。


##更多资源:



1
抱歉晚了加入这个对话。你能告诉我为什么有人会使用这些吗?你已经完美地解释了它们的工作原理等等... 我只是想知道每个使用的好处。谢谢。 - JamesG
@JamesG 在上面的另一条评论中有些解释。https://dev59.com/VG855IYBdhLWcg3waDeR#GaifEYcBWogLw_1bmpK1 - cjmling
我不知道为什么,也许有点跑题了,但是没有人提到在PHP中还有另外两个访问修饰符:抽象和最终。这个关键字只能用于PHP类,但它仍然是访问修饰符。 - bxN5
1
我建议您阅读Dhairya Lakhera在此处提供的有关抽象化的解释:https://dev59.com/fnE85IYBdhLWcg3w432o。这是对Shankar Damodaran解释的完美补充。 - Julio Marchi
@JamesG 我也想知道为什么要使用private和protected,但似乎没有答案能够解释清楚。我认为原因是private/protected方法使得重构代码更容易,因为在使用类时不应该/不能使用这些方法。除了在未来使重构和更改代码更容易之外,我不知道还有其他任何使用private/protected的理由。 - wordsforthewise

93

private - 仅可以在类内部访问

protected - 可以在类内部及继承的子类中访问

public - 可以在类外部的代码中访问

以上规则同样适用于函数和变量。


不确定这里的protected定义是否正确,从实际选择的答案来看,Protected - 只能从继承类开始访问,而不能从原始/父类访问。说“在类内部”可能会有点令人困惑。 - pal4life
7
我不这么认为,事实上看起来被选中的答案才是让人困惑的。请参考Shahid的评论。我个人认为,一个受保护的方法可以在原始类内部很好地被访问。 - Olaf
一个类可以访问另一个类的公共成员吗? - Serjas
1
@Serjas:不,只有另一个对象的,除非它们是静态方法/字段。 - DanMan
我不知道这是否适用于所有编程语言,但在PHP中,“protected”属性/方法可以在声明它的类或继承自定义属性/方法的类中访问。 - John Slegers

89

通常,将成员变量和方法的可见性设置为最低限度是良好编程实践,这有利于数据封装和良好的接口设计。在考虑成员变量和方法的可见性时,请考虑该成员在与其他对象交互中所扮演的角色。

如果你“按接口而非实现编码”,那么通常可以很容易地做出可见性决策。一般来说,除非你有充分理由来公开它们,否则变量应该是私有或受保护的。使用公共访问器(getter/setter)来限制和调节对类内部的访问。

以汽车为比喻,像速度、档位和方向等内容将是私有实例变量。你不想让驾驶员直接操纵气/油比这样的内容。相反,你只公开有限数量的操作作为公共方法。汽车的接口可能包括像accelerate(), deccelerate()/brake(), setGear(), turnLeft(), turnRight()这样的方法。

驾驶员不需要也不应该知道这些操作如何由汽车内部实现,并公开这些功能可能会给驾驶员和路上的其他人带来危险。因此,好的实践是设计公共接口并封装该接口背后的数据。

这种方法还允许你在不违反客户端代码与接口的契约的情况下更改和改进类中公共方法的实现。例如,你可以改进accelerate()方法以提高燃油效率,但使用该方法的客户端代码将保持不变;客户端代码不需要更改,但仍然可以获得你提高的效率。

编辑:由于似乎你仍然在学习面向对象的概念(这比任何语言的语法都更难掌握),我强烈建议阅读Matt Zandstra的《PHP对象、模式和实践》一书。这本书教会了我如何有效地使用OOP,而不仅仅是教我语法。我多年前就学过语法,但没有理解OOP的“为什么”,这是没有意义的。


3
这篇帖子中推荐的书真的非常优秀。目前我看过的部分相当启发人心,前几章回答了我大部分与课堂有关的问题。 - Josiah
让我真正理解面向对象的书籍,而不用用Smalltalk中的例子挤满我的思维的是David A Taylor写的《面向对象技术:管理者指南》和《面向对象技术的商业工程》。这两本书仅有100页,每本都足够轻松地在一个下午读完。当然,还有Gamma等人的《设计模式》,虽然基本方法可以简单地描述为“派生你想要变化的内容”。 - Patanjali
一个非常好的比喻。你有一个关于protected vs private的吗? - Jānis Elmeris

29

区别如下:

Public:公有变量或方法可以被类的任何用户直接访问。

Protected:受保护的变量或方法不能被类的用户访问,但可以在继承自该类的子类中访问。

Private:私有变量或方法只能从定义它的类内部访问。这意味着不能从扩展该类的子级调用私有变量或方法。


20

可见性范围抽象示例 :: 易于理解

一个属性或方法的可见性是通过在声明中添加三个关键字(公开,受保护和私有)之一来定义的。

公开:如果将属性或方法定义为公共的,则表示任何可以引用对象的东西都可以访问和操作它。

  • 抽象示例:将公共可见性作为"公共野餐",任何人都可以参加。

受保护:当将属性或方法的可见性设置为受保护时,成员只能在类本身内部以及继承和派生类中访问。(派生:一个类可以拥有另一个类的所有属性和方法)。

  • 将受保护可见性视为"公司野餐",只允许公司成员及其家庭成员而不是公众。这是最常见的范围限制。

私有:当将属性或方法的可见性设置为私有时,只有具有私有成员的类可以访问这些方法和属性(在类内部),无论其与其他类的关系如何。

  • 使用野餐比喻,将其视为"公司野餐只允许公司成员参加",而不允许家庭成员和普通公众。

15
/**
 * Define MyClass
 */
class MyClass
{
    public $public = 'Public';
    protected $protected = 'Protected';
    private $private = 'Private';

    function printHello()
    {
        echo $this->public;
        echo $this->protected;
        echo $this->private;
    }
}

$obj = new MyClass();
echo $obj->public; // Works
echo $obj->protected; // Fatal Error
echo $obj->private; // Fatal Error
$obj->printHello(); // Shows Public, Protected and Private


/**
 * Define MyClass2
 */
class MyClass2 extends MyClass
{
    // We can redeclare the public and protected method, but not private
    protected $protected = 'Protected2';

    function printHello()
    {
        echo $this->public;
        echo $this->protected;
        echo $this->private;
    }
}

$obj2 = new MyClass2();
echo $obj2->public; // Works
echo $obj2->private; // Undefined
echo $obj2->protected; // Fatal Error
$obj2->printHello(); // Shows Public, Protected2, Undefined

来源:

http://php.net/manual/zh/language.oop5.visibility.php


13

⚡️ 以下是一个简单的方法,可以记住publicprotectedprivate的作用域。

PUBLIC:

  • public作用域:公共变量/函数可供对象和其他类使用。

PROTECTED:

  • protected作用域:受保护的变量/函数可供继承当前类的所有类使用。
  • 不!对象无法访问这个作用域。

PRIVATE:

  • private作用域:仅在定义它的当前类中可见的私有变量/函数。
  • 不!继承当前类的类无法访问这个作用域。
  • 不!对象无法访问这个作用域。

请阅读PHP手册中关于方法或变量可见性的内容。


13
考虑“何时使用”:我倾向于最初将所有内容声明为私有,如果我不确定。原因是,通常更容易将私有方法公开,而不是相反。这是因为您至少可以确信私有方法仅在类本身中使用,而公共方法可能已经在各个地方使用,可能需要进行广泛的重写。
更新:现在我默认使用protected,因为我发现它足够好地封装,并且在扩展类时不会妨碍我(我尽量避免使用)。只有在有充分理由使用其他两种情况时,我才会使用它们。
一个使用private方法的好理由是,它实现了该对象固有的某些东西,即使扩展类也不应更改(如内部状态管理等客观原因,除了封装)。最终,通常仍然很容易跟踪protected方法在哪里使用,因此我现在将其默认为protected。我承认这并不是100%客观的“在战壕中”的经验。

3
请问是否需要翻译整段内容? - hakre
1
@hakre:我们应该努力实现封装的原因是为了避免状态泄漏到外部范围。protected已经做到了这一点,但你仍然可以保持其灵活性以进行扩展/继承。除非你有充分的理由将其设置为private - DanMan
那可能是我们不同意的地方:protected 实际上会泄漏到外部作用域,并且通常会妨碍你,因为它支持不良设计决策,例如隐式地偏爱继承,而更好的方法是偏向组合。这就是为什么除非你有实际要求,否则坚持使用 private 通常是编写代码的更好方式。这也将防止在实际不需要之前做出设计决策。 - hakre
1
我不会就你的总体观点争论,因为它是公平的,但是 protected 不会泄漏到外部作用域(调用/访问方法/字段的代码),而只会泄漏到内部作用域(扩展类)。虽然对你来说这可能微不足道,但这是有区别的。跟踪使用 protected 字段比跟踪使用 public 字段要容易得多。 - DanMan

7

重新提出一个老问题,但我认为一个非常好的思考方式是从你正在定义的API角度考虑。

  • public - 所有标记为public的内容都是任何使用您的类/接口/其他的人将使用并依赖的API。

  • protected - 别被骗了,这也是API的一部分!人们可以子类化、扩展您的代码并使用任何标记为protected的内容。

  • private - 私有属性和方法可以随意更改。没有人可以使用这些内容。这些是您可以进行更改而不会造成破坏性变化的唯一内容。

或者用Semver术语来说:

  • 对任何publicprotected的更改应被视为重大更改。

  • 任何新的publicprotected都应该至少是次要更改。

  • 只有对任何private的新更改可以是补丁。

因此,在维护代码方面,谨慎选择哪些内容是publicprotected是很好的,因为这些内容是您向用户承诺的内容。


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