자바:많은 필드와 캡슐화를 깔끔하게 처리하는 방법은 무엇입니까?

StackOverflow https://stackoverflow.com/questions/768783

  •  12-09-2019
  •  | 
  •  

문제

내가 일종의 RPG를 코딩하는 임무를 맡고 있다고 가정해 보겠습니다.이는 예를 들어 Character GameCharacter 지능, 피해 보너스 또는 체력과 같은 통계.

프로젝트가 끝날 때쯤에는 매우 많은 수의 필드를 처리하게 될까봐 두렵습니다. 각 필드에 대해 매우 유사한 제약 조건과 동작 집합을 따르는지 확인해야 합니다(예: 최소값과 최대값 사이에 제한이 있기를 원합니다.나는 "기본 가치"와 "임시 보너스"를 구별할 수 있기를 원합니다.나는 세터와 게터를 거치지 않고 둘 다 증가하고 감소할 수 있기를 원합니다.갑자기 모든 필드에 게터 한 개(두 개?)와 세터 네 개, 그리고 어쩌면 두어 개의 리셋터도 필요할 것입니다!10개 필드에 대해서도 많은 메서드가 모두 비슷하다는 의미입니다.

DRYness의 경우 해당 통계를 조작하는 논리를 캡슐화하기 시작했습니다. Field 클래스를 사용하여 다음과 같은 코드를 작성할 수 있습니다. intelligence.applyBonus(10) 또는 hitpoints.get() (반환된 값이 범위 내에 있는지 확인) 등.나는 해당 필드를 함께 그룹화하기 위해 클래스를 만들기 위해 많은 노력을 기울였지만 지금은 그게 요점이 아닙니다.

이제 "연결"하는 동안 이 문제가 발생했습니다. Field ~ 안으로 GameCharacter:대부분의 Java 교과서에서는 각 클래스에 공개 getter 및 setter가 있는 비공개 필드가 있어야 한다고 나와 있습니다.이론상으로는 좋은 것 같군요. 저는 이미 다음을 중심으로 전체 클래스를 구축했습니다. int;그러나 getter를 호출하여 get을 호출하는 경우 아이디어가 그다지 견고하게 들리지 않습니다.게터:

thisCharacter.getIntelligence().get() //eeek

차라리 현장에 직접 접근하는 편이 낫습니다.어쩌면 내 Python/VB [1] "배경"일 수도 있지만 나에게는 더 깨끗하고 명확하며 더 간단합니다.

thisCharacter.intelligence.get()

공개 필드의 (이론적) 문제는 내가 이에 대한 모든 통제권을 포기한다는 것입니다.예를 들어 코드베이스의 다른 지점에서 불행하게도 다음과 같은 일이 발생할 수 있습니다.

thisCharacter.intelligence = somethingThatReallyIsNull;

미묘한 버그처럼 들리네요...하지만...내 말은, 내가 정말로 그것에 대해 걱정해야 하는가?나는 절대로 할당할 계획이 없습니다. Field 직접 [2], 나는 이것이 수행되어야 하는 일이 아니라는 것을 Javadoc에 문서화했지만 여전히 여기에 익숙하지 않기 때문에 약간 찢어졌습니다.

그래서 저는 이 주제에 대해 어떻게 생각하시는지 듣고 싶습니다.캡슐화의 이점이 너무 커서 게터 게터와 세터 게터 등을 가져야 합니까?아니면 건강한 조치로 캡슐화하고 떠나야합니까? Field 로서 public 필드?


[1] 네, 알겠습니다.나는 잊으려고 노력해 왔습니다.하지만 우리는 최근에 약간의 C#과 사람을 보았지만 속성이 좋지 않습니다.아 글쎄.

[2] 생성자를 제외하고!그리고 getter는 잘못된 생성자로부터 나를 구해줄 수 없습니다.

도움이 되었습니까?

해결책

내 경험에 따르면, 도움이 필요한 상황에서는 많은 필드의 수, 성격, 이름 지정 및 유형은 매우 유연하고 프로젝트 수명 전반에 걸쳐 변경될 가능성이 높기 때문에 필드 대신 일종의 맵이 필요할 수 있습니다.

예를 들어 키에서 값으로의 속성 매핑이 있습니다.

속성을 가져오고 설정하기 위한 공개 호출을 제공하되 모든 사람이 해당 속성을 사용하도록 허용하지 마십시오(또는 사용하지 않도록 확인).대신, 관심 있는 각 속성을 나타내는 클래스를 생성하세요. 그러면 해당 클래스는 해당 속성을 조작하기 위한 모든 기능을 제공합니다.예를 들어 Strength가 있는 경우 특정 Player 개체로 초기화된 "StrengthManipulation" 클래스를 가질 수 있으며 getter, setter(모두 적절한 유효성 검사 및 예외 포함) 및 보너스로 강도 계산 등을 제공할 수 있습니다. .

이것의 한 가지 장점은 플레이어 클래스에서 속성 사용을 분리한다는 것입니다.따라서 이제 Intelligence 속성을 추가하면 힘만 조작하는 모든 것을 처리하고 다시 컴파일할 필요가 없습니다.

필드에 직접 액세스하는 것은 나쁜 생각입니다.VB(적어도 이전 VB에서는)의 필드에 액세스할 때 일반적으로 속성 getter 및 setter를 호출하고 VB는 단순히 () 호출을 숨깁니다.내 견해로는 사용하는 언어의 관례에 적응해야 한다는 것입니다.C, C++, Java 등에는 필드와 메소드가 있습니다.메서드를 호출할 때는 그것이 호출이고 다른 일이 발생할 수 있음을 분명히 하기 위해 항상 ()가 있어야 합니다(예: 예외가 발생할 수 있음).어느 쪽이든 Java의 장점 중 하나는 보다 정확한 구문과 스타일입니다.

VB에서 Java 또는 C++로 변환하는 것은 대학원 과학 글쓰기에 문자 메시지를 보내는 것과 같습니다.

지금:일부 유용성 연구에 따르면 생성자에 대한 매개변수를 갖지 않고 필요한 경우 모든 설정자를 구성하고 호출하는 것이 더 낫다는 것을 보여줍니다.

다른 팁

관점에서 생각하고 있는 것 같군요. if(player.dexterity > monster.dexterity) attacker = player.당신은 더 다음과 같이 생각해야합니다 if(player.quickerThan(monster)) monster.suffersAttackFrom(player.getCurrentWeapon()).단순한 통계를 가지고 장난을 치지 말고, 실제 의도를 표현한 다음, 해야 할 일을 중심으로 수업을 디자인하세요.어쨌든 통계는 경찰에 불과합니다.당신이 정말로 관심을 갖는 것은 플레이어가 어떤 행동을 할 수 있는지 여부, 또는 그들의 능력/능력 대 일부 참조입니다."플레이어 캐릭터가 충분히 강한가"라는 관점에서 생각해보세요(player.canOpen(trapDoor)) 대신 "캐릭터의 힘이 50 이상입니까?"

Steve Yegge는 이러한 문제를 다룬 매우 흥미로운(길다면) 블로그 게시물을 게시했습니다. 유니버설 디자인 패턴.

나에게는 'thisCharacter'가 배후의 지능을 처리하기 위한 'intelligence' 객체를 가질 수도 있는 것처럼 보이지만 그것이 공개되어야 하는지에 대해서는 의문이 듭니다.이를 처리하는 개체 대신 thisCharacter.applyInt 및 thisCharacter.getInt를 노출해야 합니다.구현을 그렇게 노출하지 마십시오.

귀하의 필드를 비공개로 유지하십시오!API를 너무 많이 노출하고 싶지는 않습니다.비공개였던 것을 공개로 만들 수는 있지만 향후 릴리스에서는 그 반대는 불가능합니다.

그것을 다음 MMORPG로 만들 것이라고 생각해보세요.버그, 오류 및 불필요한 악의 여지가 많습니다.불변 속성이 최종인지 확인하세요.

최소한의 인터페이스(재생, 중지, 메뉴)를 갖고 있으면서도 내부에는 이러한 기술적 기능이 있는 DVD 플레이어를 생각해 보십시오.프로그램에서 중요하지 않은 모든 것을 숨기고 싶을 것입니다.

귀하의 주된 불만은 setter/getter 메소드의 추상화가 아니라 이를 사용하기 위한 언어 구문에 있는 것 같습니다.즉, C# 스타일 속성과 같은 것을 선호할 것입니다.

그렇다면 Java 언어는 상대적으로 제공할 수 있는 것이 거의 없습니다.getter 또는 setter로 전환해야 할 때까지 직접 필드 액세스는 괜찮습니다. 그런 다음 일부 리팩토링을 수행해야 합니다(전체 코드베이스를 제어하는 ​​경우 괜찮을 수도 있음).

물론 Java 플랫폼이 요구사항이지만 언어가 그렇지 않은 경우 다른 대안이 있습니다.예를 들어 Scala에는 매우 훌륭한 속성 구문이 있으며 이러한 프로젝트에 유용할 수 있는 다른 많은 기능도 있습니다.그리고 무엇보다도 JVM에서 실행되므로 Java 언어로 작성할 때와 동일한 이식성을 얻을 수 있습니다.:)

여기에 보이는 것은 복합 모델의 단일 레이어입니다.

모델을 하위 수준 모델 세트로 유지하는 대신 모델에 추상화를 추가하는 메서드를 추가할 수 있습니다.

필드는 최종적이어야 하므로 공개로 설정하더라도 실수로 할당할 수 없습니다. null 그들에게.

"get" 접두사는 모든 getter에 존재하므로 새로운 문제라기보다는 초기 모습에 더 가깝습니다.

캡슐화의 이점이 너무 커서 게터 게터와 세터 게터 등을 가져야 합니까?아니면 건전한 조치로 캡슐화하고 필드를 공개 필드로 남겨 두어야 합니까?

IMO, 캡슐화는 개인 필드 주위에 getter/setter를 래핑하는 것과 관련이 없습니다.소량으로 사용하거나 범용 라이브러리를 작성할 때 절충안이 허용됩니다.하지만 당신이 설명하는 것과 같은 시스템을 선택하지 않은 채로 두면, 반패턴.

getter/setter의 문제점은 해당 메소드를 사용하는 객체와 시스템의 나머지 부분 사이에 지나치게 긴밀한 결합을 생성한다는 것입니다.

실제 캡슐화의 장점 중 하나는 getter 및 setter의 필요성을 줄여 프로세스의 나머지 시스템에서 개체를 분리한다는 것입니다.

setIntelligence를 사용하여 GameCharacter 구현을 노출하는 대신 GameCharacter에 게임 시스템에서의 역할을 더 잘 반영하는 인터페이스를 제공하는 것은 어떨까요?

예를 들면 다음과 같습니다.

// pseudo-encapsulation anti-pattern
public class GameCharacter
{
  private Intelligence intelligence;

  public Intelligence getIntelligence()
  {
    return intelligence
  }

  public void setIntelligence(Intelligence intelligence)
  {
    this.intelligence = intelligence;
  }
}

이것을 시도해 보는 것은 어떨까요?:

// better encapsulation
public class GameCharacter
{
  public void grabObject(GameObject object)
  {
    // TODO update intelligence, etc.
  }

  public int getIntelligence()
  {
    // TODO
  }
}

또는 더 나은:

// still better
public interface GameCharacter
{
  public void grabObject(GameObject object); // might update intelligence
  public int getIntelligence();
}

public class Ogre implements GameCharacter
{
  // TODO: never increases intelligence after grabbing objects
}

즉, GameCharacter는 GameObject를 잡을 수 있습니다.동일한 GameObject를 잡는 각 GameCharacter의 효과는 다양할 수 있지만, 세부 사항은 각 GameCharacter 구현 내에 완전히 캡슐화되어 있습니다.

예를 들어 GameCharacter가 GameObject를 잡을 때 발생할 수 있는 자체 지능 업데이트(범위 확인 등)를 어떻게 처리하는지 주목하세요.세터(및 이를 갖는 데 따른 합병증)가 사라졌습니다.상황에 따라 getIntelligence 메소드를 모두 사용하지 않아도 될 수 있습니다.Allen Holub은 이 아이디어를 논리적 결론, 하지만 이러한 접근 방식은 그다지 일반적이지 않은 것 같습니다.

Uri의 답변(제가 전적으로 지지하는) 외에도 데이터에 속성 맵을 정의하는 것을 고려해 보시기 바랍니다.이렇게 하면 프로그램이 매우 유연해지며 필요하지 않다는 사실조차 인식하지 못하는 많은 코드가 제외됩니다.

예를 들어, 속성은 화면에서 바인딩하는 필드, 데이터베이스에서 바인딩하는 필드, 속성이 변경될 때 실행할 작업 목록(재계산된 적중%는 힘과 민첩 모두에 적용될 수 있음)을 알 수 있습니다. )

이렇게 하면 클래스를 DB에 쓰거나 화면에 표시하기 위한 속성당 코드가 없습니다.간단히 속성을 반복하고 내부에 저장된 정보를 사용하면 됩니다.

스킬에도 동일한 내용이 적용될 수 있습니다. 실제로 속성과 스킬은 동일한 기본 클래스에서 파생될 수 있습니다.

이 길을 따라가다 보면 속성과 기술을 정의하는 매우 심각한 텍스트 파일을 발견하게 될 것입니다. 그러나 새로운 기술을 추가하는 것은 다음과 같이 쉽습니다.

Skill: Weaving
  DBName: BasketWeavingSkill
  DisplayLocation: 102, 20  #using coordinates probably isn't the best idea.
  Requires: Int=8
  Requires: Dex=12
  MaxLevel=50

어떤 시점에서는 이와 같은 기술을 추가하는 데 코드 변경이 전혀 필요하지 않으며 모든 작업을 데이터에서 매우 쉽게 수행할 수 있으며 모든 데이터는 클래스에 연결된 단일 기술 개체에 저장됩니다.물론 동일한 방식으로 작업을 정의할 수도 있습니다.

Action: Weave Basket
  text: You attempt to weave a basket from straw
  materials: Straw
  case Weaving < 1
    test: You don't have the skill!
  case Weaving < 10
    text: You make a lame basket
    subtract 10 straw
    create basket value 8
    improve skill weaving 1%
  case Weaving < 40
    text: You make a decent basket
    subtract 10 straw
    create basket value 30
    improve skill weaving 0.1%
  case Weaving < 50
    text: You make an awesome basket!
    subtract 10 straw
    create basket value 100
    improve skill weaving 0.01%
  case Weaving = 50
    text: OMG, you made the basket of the gods!
    subtract 10 straw
    create basket value 1000

이 예제는 상당히 고급이지만 코드 없이 어떻게 수행되는지 시각화할 수 있습니다."속성/기술"이 실제로 멤버 변수인 경우 코드 없이 이와 같은 작업을 수행하는 것이 얼마나 어려울지 상상해 보십시오.

Eclipse의 EMF와 같은 "모델링" 프레임워크 사용을 고려해보세요.여기에는 Eclipse EMF 편집기, 일반 XML 또는 Rational Rose와 같은 것을 사용하여 객체를 정의하는 것이 포함됩니다.들리는 것만큼 어렵지는 않습니다.속성, 제약 조건 등을 정의합니다.그런 다음 프레임 워크는 귀하를위한 코드를 생성합니다.Getter 및 Setter 메소드와 같은 추가 부품에 @generated 태그를 추가합니다.코드를 사용자 정의하고 나중에 손으로 또는 일부 GUI를 통해 편집하고 Java 파일을 재생할 수 있습니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top