그래서 최근에 코드에 몇 가지 주요 리팩토링을 만들었습니다. 내가하려고했던 주요 작업 중 하나는 클래스를 데이터 객체와 작업자 객체로 분리하는 것이 었습니다. 이것은 Clean Code 의이 섹션에서 영감을 얻었습니다 .
하이브리드
이 혼동은 때때로 반 객체와 반 데이터 구조 인 불행한 하이브리드 데이터 구조로 이어집니다. 중요한 기능을 수행하는 함수가 있으며, 모든 의도와 목적을 위해 비공개 변수를 공개하고 다른 외부 함수가 해당 변수를 절차 프로그램에서 사용하는 방식으로 사용하도록 유혹하는 공용 변수 또는 공개 접근 자 및 변경자 데이터 구조.
이러한 하이브리드는 새로운 기능을 추가하기 어렵지만 새로운 데이터 구조를 추가하기 어렵게 만듭니다. 그들은 두 세계 중 최악입니다. 작성하지 마십시오. 그것들은 저자가 기능이나 유형으로부터 보호가 필요한지 확실하지 않은, 또는 더 나쁘고, 불분명 한 디자인을 나타냅니다.
최근에 나는 작업자 객체 ( 방문자 패턴 을 구현하는 ) 중 하나에 대한 코드를 보고 이것을 보았습니다.
@Override
public void visit(MarketTrade trade) {
this.data.handleTrade(trade);
updateRun(trade);
}
private void updateRun(MarketTrade newTrade) {
if(this.data.getLastAggressor() != newTrade.getAggressor()) {
this.data.setRunLength(0);
this.data.setLastAggressor(newTrade.getAggressor());
}
this.data.setRunLength(this.data.getRunLength() + newTrade.getLots());
}
나는 즉시! “이 논리가에 있어야 기능 질투 나 자신에게 말했다 Data
– 특히 클래스 handleTrade
방법. handleTrade
그리고 updateRun
해야 항상 함께 일”. 그러나 “데이터 클래스는 public
데이터 구조 일뿐입니다. 시작하면 하이브리드 객체가 될 것입니다.”
더 나은 이유는 무엇입니까? 어떻게해야합니까?
답변
인용 한 텍스트에는 좋은 조언이 있지만, 구조체와 같은 것을 의미한다고 가정하면“데이터 구조”를“레코드”로 대체 할 것입니다. 레코드는 단순한 데이터 집계입니다. 그것들은 변경 가능하고 (따라서 기능 프로그래밍 사고 방식에서 상태가 좋을 수도 있음), 내부 상태가 없으며 보호해야 할 불변 요소가 없습니다. 사용법을 쉽게하는 레코드에 조작을 추가하는 것은 완전히 유효합니다.
예를 들어, 3D 벡터가 멍청한 기록이라고 주장 할 수 있습니다. 그러나 이렇게하여 메소드를 추가하지 못하게해서는 add
안되므로 벡터를 쉽게 추가 할 수 있습니다. 비헤이비어를 추가해도 멍청하지 않은 레코드가 하이브리드로 바뀌지 않습니다.
이 줄은 객체의 공용 인터페이스로 캡슐화를 깨뜨릴 수있을 때 넘어갑니다. 직접 액세스 할 수있는 일부 내부 객체가 있으므로 객체가 잘못된 상태가됩니다. 그 날 것으로 보인다 Data
상태를 가지고, 그리고 그것은 잘못된 상태로 할 수있다 :
- 거래를 처리 한 후 마지막 공격자가 업데이트되지 않을 수 있습니다.
- 새로운 거래가 발생하지 않아도 마지막 공격자는 업데이트 될 수 있습니다.
- 공격자가 업데이트 된 경우에도 실행 길이에 이전 값이 유지 될 수 있습니다.
- 기타
데이터에 유효한 상태가 있으면 코드로 모든 것이 정상이며 계속 진행할 수 있습니다. 그렇지 않은 경우 : Data
클래스는 자체 데이터 일관성을 담당합니다. 거래를 처리하는 것이 항상 공격자 업데이트와 관련된 경우이 동작은 Data
클래스의 일부 여야합니다 . 공격자를 변경하는 데 실행 길이를 0으로 설정하면이 동작은 Data
클래스의 일부 여야합니다 . Data
멍청한 기록은 없었어요 공개 세터를 추가하여 이미 하이브리드로 만들었습니다.
이러한 엄격한 책임을 완화하는 것을 고려할 수있는 한 가지 시나리오 Data
가 있습니다. 프로젝트가 개인용이므로 공용 인터페이스의 일부가 아닌 경우에도 클래스의 올바른 사용을 보장 할 수 있습니다. 그러나 이로 Data
인해 중앙 위치에서 코드를 수집하지 않고 코드 전체의 일관성 을 유지 해야합니다.
필자는 최근 캡슐화에 대한 답변을 썼습니다. 캡슐화가 무엇인지, 어떻게 캡슐화 할 수 있는지에 대해 자세히 설명합니다.
답변
사실 handleTrade()
과 updateRun()
항상 함께 발생 (그리고 두 번째 방법은 방문자에 실제로 데이터 객체에 여러 가지 다른 방법을 호출)의 냄새 시간적 커플 . 즉 , 특정 순서로 메소드를 호출 해야하며 , 메소드를 순서대로 호출하면 최악의 상황이 발생하거나 의미있는 결과를 얻지 못할 것으로 생각합니다. 안좋다.
일반적으로 종속성을 리팩터링하는 올바른 방법은 각 메소드가 다음 메소드에 제공되거나 직접 수행 될 수있는 결과를 리턴하는 것입니다.
오래된 코드 :
MyObject x = ...;
x.actionOne();
x.actionTwo();
String result = x.actionThree();
새로운 코드 :
MyObject x = ...;
OneResult r1 = x.actionOne();
TwoResult r2 = r1.actionTwo();
String result = r2.actionThree();
여기에는 몇 가지 장점이 있습니다.
- 별도의 관심사를 별도의 오브젝트 ( SRP ) 로 이동합니다 .
- 시간 결합을 제거합니다. 메소드를 순서대로 호출하는 것은 불가능하며 메소드 서명은 호출 방법에 대한 암시 적 문서를 제공합니다. 문서를보고, 원하는 객체를보고, 역으로 작업 한 적이 있습니까? 객체 Z를 원하지만 Z를 얻으려면 Y가 필요합니다. Y를 얻으려면 X가 필요합니다. Aha! X를 얻는 데 필요한 W가 있습니다. 모두 함께 묶으면 이제 W를 사용하여 Z를 얻을 수 있습니다.
- 이와 같이 객체를 분할하면 객체를 변경할 수 없을 가능성이 높아 지므로이 질문의 범위를 벗어나는 많은 장점이 있습니다. 빠른 변화는 불변의 객체가 더 안전한 코드를 만드는 경향이 있다는 것입니다.
답변
내 관점에서 볼 때 클래스 에는 “상태 값 (구성원 변수) 및 동작 구현 (구성원 함수, 메서드)”이 포함되어야합니다.
“불행한 하이브리드 데이터 구조”는 클래스 상태 멤버 변수 (또는 해당 게터 / 세터)를 공개해서는 안되는 공개로 만들면 나타납니다.
따라서 데이터 데이터 객체와 작업자 객체에 대해 별도의 클래스가 필요하지 않습니다.
상태 멤버 변수를 비공개로 유지할 수 있어야합니다 (데이터베이스 레이어는 비공개 멤버 변수를 처리 할 수 있어야합니다).
Feature envy는 다른 클래스의 메소드를 과도하게 사용하는 클래스입니다. Code_smell을 참조하십시오 . 메소드와 상태가있는 클래스를 사용하면 이것을 제거 할 수 있습니다.