2010-11-25 3 views
4

나는 공개개인 액세스 수정자인 이유를 이해할 수 있습니다.이 두 단어는 거의 모든 언어에서도 볼 수 있습니다. 패키지 수식어가있는 이유를 이해할 수 있습니다. 클래스가 서로 긴밀하게 상호 작용하고 공용 상호 작용에 적합하지 않을 수 있습니다 (예 : 의존하는 경우). 클래스 내부에 대한 지식, 아마도 비밀을 밝혀 내었거나 어쩌면 언제든지 변경 될 수 있고 의존하면 모든 기존 코드가 손상 될 수 있기 때문일 수 있습니다. 그러나 식별자로 보호해야하는 이유는 무엇입니까? 나를 잘못 이해하지 말라. 무엇이 을 보호하는지 알고 싶다. 의미는 없지만, 하위 클래스가 서브 패키지이기 때문에 특정 인스턴스 변수에 액세스하거나 특정 메소드를 사용하려는 이유는 무엇입니까? 에 대한 실제 사용 사례는 무엇입니까??OO 언어가 실제로 PROTECTED 액세스 수정자를 필요로하는 이유는 무엇입니까?

(그리고 성능 인스턴스 변수에 대한 인수가 포함되지 않기 때문에, JIT 컴파일러는 항상 인라인 접근 방법은 제로로 자신의 호출 오버 헤드를 감소 할 수 있기 때문에)

+2

은 그냥 자바 독에서 몇 가지 클래스를 통해 훑어 당신은 내가 공개 방법은에서 사람에게 전혀 해를 끼치 지 이루어지지했을 사례 만보고 "실제 사용 사례"의 무리 : – zneak

+0

를 볼 수 있습니다 (패키지 클래스가 처음부터 패키지 외부로 서브 클래스 화되도록 설계되지 않았기 때문에) 패키지 메소드가 보호 된 메소드보다 더 이해하기 쉬운 곳에서 사용할 수 있습니다. – Mecki

답변

15

공공 방법은 공용 인터페이스의 일부입니다. 개인 메소드는 내부 메소드입니다. 보호 된 메소드는 확장 포인트입니다.

protected을 사용하면이 메소드를 공용 인터페이스의 일부로 만들지 않고도 클래스의 기능을 재정 의하여 재정의 할 수 있습니다.

보호 된 메소드는 서브 클래스에서 재사용 할 수 있지만 공용 인터페이스의 일부일 필요는없는 일반적인 메소드입니다.

예를 들어, Java 컬렉션 프레임 워크에는 AbstractList 클래스가 있습니다. 변형의 수를 계산하기 위해 모든 서브 클래스

modCount 필드를 사용
  • (증가)이 modCount 필드 및 보호 removeRange 방법을 보호하고있다. AbstractList에 의해 반환되는 Iterator 해당 필드

  • removeRange 방법을 사용하는 대신에 그들이 다시 정의 할 필요없이, 서브 클래스에서 재사용 할 수 있습니다.

See this API 디자인에 관한 Josh Bloch의 관련 프리젠 테이션.

코멘트와 Bloch의 프리젠 테이션에서 언급했듯이 - 클래스를 잘 문서화하십시오. 그리고 그것이 상속을위한 것이라면 - 여분의 노력을하십시오.

+0

비 - 하위 클래스가 수정 횟수를 쿼리 할 수없는 이유는 무엇입니까? 이 정보에 대해 "악"이란 무엇입니까? 그리고 왜 나머지 세계는 removeRange 호출을 할 수 없습니까? – Mecki

+0

수정 횟수는 추상 목록에 의해 생성 된 반복자에만 중요합니다. 나머지 세계는 removeRange를 호출하면 안됩니다 - 잘 모르겠습니다. 나는 Bloch이 강연에서 이것에 관해 언급하는 것을 기억하지만 그것을 발견 할 수 없다고 생각한다. – Bozho

+0

정보의 일부가 다른 사람들에게 흥미롭지 않다는 사실은 나머지 세계가 그 정보에 액세스하는 것을 허용하지 않는다는 주장입니다. 사실 그것은 흥미 롭지 않기 때문에 나머지 국가들은 할 수 있더라도 어쨌든 결코 액세스하지 않을 것입니다. 그리고 나머지 세계가 AbstractList의 서브 클래스를 서브 클래스 화하는 것만으로이 서브 클래스를 사용하고 다른 메소드를 통해이 정보를 제공 할 수 있습니까? – Mecki

-1

아마, 결혼하고 다른 나라에 살고 있더라도 자녀와 공유 할 정보/재산이있을 것입니다. 현재 protected 수식어가없는 언어가 있습니다.

+0

하지만 나머지 애플리케이션과 공유하고 싶지 않은 이유는 무엇입니까? 그리고 나머지 부분과 공유하고 싶지 않다면 왜 "패키지"를 사용하지 않을까요? (패키지가 "샘플"을 따르는 "가족"으로 볼 수있는 곳) – Mecki

+0

나는 비유를 얻지도 못합니다. 그것은 비유입니다, 그렇죠? – Teekin

+0

@Helgi @Mecki 유추는 다음과 같습니다 : "패키지"는 같은 장소에서의 생활을 의미하고, "보호 된"는 가족을 의미합니다. 가족과 함께 나눌 것이 있지만 이웃과 함께하는 것은 아닙니다. 그리고 "가족"은 같은 장소 (즉, 같은 패키지)에 사는 것을 의미하지 않습니다. 나는 이번에 잘 설명했으면 좋겠다. :) – khachik

0

가능한 확장자의 마음을 완전히 알지 못하면 바로 가기로 보입니다. 기본 클래스에 5 또는 6 개의 속성이 있다고 가정 해보십시오. 당신은 분명히 그 속성 (또는 그들의 설정 메소드)을 공개하지 않기를 바랄 것이다. 그러나 Extender가 이러한 속성 값을 변경하는 함수를 작성하려고 할 수 있음을 알 수 있습니다. 그래서 당신은 그 (또는 더 나은, 그들의 세트) 보호합니다. 익스텐더에는 괜찮 으면서도 오래된 소비 코드에는 적합하지 않은 디자인 의존성이 항상있을 것입니다.

즉, 나는 학생들에게 "보호받는 사람 목록"이라고 말했습니다. 왜냐하면 당신이 보호받는 것을 바꾸면, 당신은 누가 그것을 의지했는지 찾아야 만합니다. 미래의 알려지지 않은 확장자에 그것을 드러내 기 전에 어떤 것에 대해 정말로 확신하십시오.

+0

왜 Extender가 그렇게 할 수 있겠는가? 나머지 세계의 일부인 증량제가 ... 글쎄요 ... "세계의 나머지 부분"과 다른 점은 무엇입니까? – Mecki

+0

당신은 BankAccount를 썼습니다. 누군가가 SavingsAccount 또는 TaxProtectedAccount를 작성할 수 있으며 해당 클래스의 코드가 기본 클래스의 잔액 한도 또는 초과 인출 한도 또는 기타 속성을 변경해야 할 수도 있다고 생각합니다. –

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

를이 경우, 보호 Bar의 요소, 그리고 반대를 사용할 필요가 Foo을합니다. 상위 클래스가 하위 클래스의 논리에 액세스하려고하지만 공개적으로 노출되지 않도록하려면 protected 수정자를 선택할 수 없습니다. 이를 템플릿 메서드 패턴이라고하며 자주 사용됩니다. (죄송합니다 실제 예. Head to Wikipedia if you want some. 제공하지 않는)

+0

이 예제에서 processElement를 공개로 설정하는 것은 무엇이 좋을까요? 그것은 무엇이든 부러 뜨릴 것인가? 비밀 정보를 공개 하시겠습니까? 바깥 세상의 프로그래머가 클래스를 서브 클래스 화하도록 허용하면 정보가 숨겨지기 때문에 비밀 정보가 아닙니다. – Mecki

+0

@Mecki 이것은 간단한 예입니다. 그것이 공개적이라면 나쁜 일은 없을 것입니다. 그러나이 방법은 비공개가 선호되는 방식으로 내부를 처리 할 수 ​​있습니다. 그리고 수퍼 클래스는 그것을 필요로하기 때문에 private가 될 수 없으므로'protected' 만 남습니다. – zneak

+0

@Mecki'private' 메소드의 필요성을 이해한다면 왜 템플릿 메소드가 항상'public'이 될 수 있는지 이해할 수있을 것입니다. 관련 상속이 없다면, 그러한 메소드는 '비공개'가 될 것입니다. 그러나 슈퍼 클래스에서 필요하기 때문에'protected'가 필요합니다. – zneak

0

언어는 protected 액세스 수정을하지 않는 존재, 모든 모든 방법이 공개되어에서 를 액세스 수정이 없어도 언어는 사실을, 이러한 언어는 일반적으로 "가장 순수한"OO 언어로 간주되며 실제로는 이 아니며이 아니고 실제로는 protected 액세스 한정자임을 나타냅니다.

0

공용 상속 가능한 클래스의 공용 멤버는 모든 정상적으로 작동하는 하위 클래스에 바인딩되는 전체 세계와 계약을 구성합니다. 보호 된 멤버는 즉시 노출되는 클래스에만 바인딩되는 즉각적인 하위 클래스와 계약을 구성합니다. 서브 클래스는 보호 된 멤버를 자신의 서브 클래스에 노출 할 의무가 없습니다 (실제로 그러한 노출을 거부하는 규칙이 있어야하며, 특정 보호 된 멤버는 기본적으로 하위 클래스에서만 사용할 수 있도록 지정해야합니다. 서브 클래스는 서브 클래스에서도 사용할 수 있도록 지정합니다).

어떤 사람들은 부모에 의해 노출 된 보호 된 구성원에 대한 액세스를 허용하지 않는 클래스에 대한 생각에 냉담 해 할지도 모르지만 그러한 행동은 어떤 식 으로든 Liskov 대체 원칙을 위반하지 않습니다. LSP에서는 코드가 기본 유형 객체를 예상 할 때 파생 유형 객체를 수신 할 수있는 경우 파생 유형 객체는 기본 유형 객체가 수행 할 수있는 모든 작업을 수행 할 수 있어야한다고 명시합니다. 따라서 MoeMoe에서 파생 된 Larry 유형 및 유형을 공개적으로 지원하는 경우 Moe 유형의 매개 변수를 허용하고 해당 기능을 사용하려고 시도하는 코드는 Larry을 부여하면 실패합니다. 그러한 실패는 LSP를 위반하게됩니다.

그러나 MoeLarry이 지원하지 않는 protected 기능을 포함한다고 가정합니다. 이 기능을 사용할 수있는 유일한 클래스는 Moe에서 직접 파생 된 클래스이거나이를 지원하는 Moe의 자손입니다. CurlyMoe에서 파생 된 경우 Moe의 하위 유형이 모두 지원되는지 여부를 염두에 두지 않고도 해당 기능을 사용할 수 있습니다. Curly의 기본 객체는 Moe이거나 파생물 일 수 있습니다. 기능을 지원하지 않음) - Moe, 마침표가됩니다.

파생 된 유형에서 해당 필드를 사용하는 기반의 공용 멤버를 손상시키는 방식으로 보호 필드가 LSP 관련 문제를 일으킬 수 있습니다. 반면에, 보호 된 변수를 사용할 필요는 없습니다.하위 유형이 예상되는 기본 유형의 동작과 다른 방식으로 가상 멤버를 구현하면 기본 클래스의 보호 멤버를 건드리지 않고도 기본 유형의 공용 멤버를 손상시킬 수 있습니다.

0

개인적으로 공공 계약에서 소음을 없애고 싶을 때 개인적으로 보호 된 수정자를 사용합니다. 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(); 
    } 
} 

이제 다음은

는 콘크리트 위젯 쿼리 처리기은 다음과 같은 형태가 될 것이다 CachingQueryProvider 클래스에 추가 된 캐싱에 특정한 것을 사용하지 않을 것입니다. 나는 필요에 따라 캐싱을 추가/제거하거나 최소한의 노력으로도 캐싱 구현을 완전히 변경할 수 있습니다.

IQueryHandler<WidgetCommand, Widget> widgetQueryHandler; 
var widgets = widgetQueryHandler.Execute(myWidgetCommand);