객체를 변경하는 메소드에 객체를 전달하는 것이 일반적인 패턴입니까?


17

Martin Fowler의 Refactoring book 에서 일반적인 코드 냄새에 대해 읽고 있습니다. 그런 맥락에서, 나는 코드베이스에서보고있는 패턴에 대해 궁금해했고, 객관적으로 패턴을 반 패턴으로 간주 할 수 있었다.

패턴은 하나 이상의 메소드에 대한 인수로 오브젝트가 전달되는 패턴으로, 모두 오브젝트의 상태를 변경하지만 오브젝트를 리턴하는 것은 없습니다. 따라서 C # /. NET의 참조 특성에 따라 패스에 의존합니다.

var something = new Thing();
// ...
Foo(something);
int result = Bar(something, 42);
Baz(something);

나는 (특히 메소드의 이름이 적절하게 지정되지 않은 경우) 객체의 상태가 변경되었는지 이해하기 위해 그러한 메소드를 조사해야합니다. 여러 수준의 콜 스택을 추적해야하므로 코드 이해가 더 복잡해집니다.

새로운 상태의 다른 (복제 된) 객체 또는 콜 사이트에서 객체를 변경하는 데 필요한 모든 것을 반환하도록 이러한 코드를 개선하고 싶습니다.

var something1 =  new Thing();
// ...

// Let's return a new instance of Thing
var something2 = Foo(something1);

// Let's use out param to 'return' other info about the operation
int result;
var something3 = Bar(something2, out result);

// If necessary, let's capture and make explicit complex changes
var changes = Baz(something3)
something3.Apply(changes);

나에게 그것은 첫 번째 패턴이 가정에서 선택되는 것처럼 보입니다.

  • 작업이 적거나 코드 줄이 더 적습니다.
  • 그것은 모두 변화 오브젝트에 우리를 할 수 있다는 정보의 다른 조각을 반환
  • 인스턴스 수가 적으므로 더 효율적입니다 Thing.

대안을 설명하지만 제안하려면 원래 솔루션에 대한 논쟁이 필요합니다. 원래 솔루션이 반 패턴이라는 주장을 할 수있는 주장이 있다면 무엇입니까?

그리고 대체 솔루션에 어떤 문제가 있습니까?



1
@DaveHillier 감사합니다.이 용어에 익숙했지만 연결하지 못했습니다.
Michiel van Oosterhout

답변:


9

그렇습니다. 원래의 솔루션은 설명하는 이유로 반 패턴입니다. 어떻게 진행되고 있는지에 대한 추론을 어렵게 만들고, 객체는 자체 상태 / 구현 (캡슐화 중단)을 책임지지 않습니다. 또한 이러한 모든 국가 변경 사항은 메서드의 암시 적 계약이므로 변경되는 요구 사항에 직면하여 해당 메서드가 취약 해집니다.

즉, 솔루션에는 자체 단점이 있으며 그중 가장 명백한 것은 복제 객체가 크지 않다는 것입니다. 큰 물체의 경우 속도가 느릴 수 있습니다. 코드의 다른 부분이 이전 참조를 유지하는 경우 오류가 발생할 수 있습니다 (설명한 코드베이스의 경우 일 수 있음). 이러한 객체를 명시 적으로 변경할 수 없게 만들면 이러한 문제 중 적어도 몇 가지가 해결되지만 더 급격한 변화입니다.

객체가 작고 일시적이지 않은 한 (불변성을위한 좋은 후보로 만들지 않는 한) 더 많은 상태 전이를 객체 자체로 옮기는 경향이 있습니다. 이를 통해 이러한 전환의 구현 세부 사항을 숨기고 해당 상태 전환이 발생하는 사람 / 주요 / 위치에 대해 더 강력한 요구 사항을 설정할 수 있습니다.


1
예를 들어, "파일"개체가있는 경우 상태 변경 방법을 해당 개체로 이동하려고 시도하지 않습니다. 이는 SRP를 위반하는 것입니다. "File"과 같은 라이브러리 클래스 대신 자체 클래스가있는 경우에도 유효합니다. 모든 상태 전환 로직을 객체 클래스에 넣는 것은 실제로 의미가 없습니다.
Doc Brown

@Tetastyn 나는 이것이 오래된 대답이라는 것을 알고 있지만 마지막 단락에서 구체적인 용어로 제안을 시각화하는 데 어려움을 겪고 있습니다. 좀 더 자세히 설명해 주시겠습니까?
AaronLS

@AaronLS- Bar(something)의 상태를 수정하는 대신 의 유형 의 멤버를 something만듭니다 . 가능성이 돌연변이입니다 또한 보호 OO 도구 (개인 상태, 인터페이스 등)을 사용할 수 있도록하면서, 국가의를Barsomethingsomething.Bar(42)somethingsomething
Telastyn

14

메소드의 이름이 적절하지 않은 경우

실제로, 그것은 실제 코드 냄새입니다. 변경 가능한 객체가있는 경우 상태를 변경하는 메소드를 제공합니다. 더 많은 문장의 작업에 포함 된 그러한 방법에 대한 호출이있는 경우 해당 작업을 자체 방법으로 리팩터링하는 것이 좋습니다. 그러나 Fooand와 같은 메소드 이름이 Bar없지만 객체를 변경한다는 것을 분명히하는 메소드는 여기에 문제가 없습니다. 에 대해 생각하다

void AddMessageToLog(Logger logger, string msg)
{
    //...
}

또는

void StripInvalidCharsFromName(Person p)
{
// ...
}

또는

void AddValueToRepo(Repository repo,int val)
{
// ...
}

또는

void TransferMoneyBetweenAccounts(Account source, Account destination, decimal amount)
{
// ...
}

또는 그와 같은 것-여기에 해당 메소드에 대해 복제 된 객체를 반환 할 이유가 없으며 전달 된 객체의 상태가 변경 될 것이라는 것을 이해하기 위해 구현을 조사 할 이유가 없습니다.

부작용을 원하지 않으면 객체를 변경할 수 없게 만들면 위와 같은 방법으로 원래 객체를 변경하지 않고 변경된 (복제 된) 객체를 반환합니다.


이름 바꾸기 방법 리팩토링은 부작용을 명확하게하여 상황을 개선 할 수 있습니다. 간결한 분석법 이름을 사용할 수 없도록 수정하면 어려워 질 수 있습니다.
Michiel van Oosterhout

2
@michielvoo : consise method naming이 가능하지 않은 경우, 메소드는 수행하는 작업에 대한 기능적 추상화를 작성하는 대신 잘못된 것을 그룹화합니다 (그리고 부작용이 있거나 없거나).
Doc Brown

4

그렇습니다 . 예기치 않은 부작용이 나쁘다고 지적하는 많은 사람들의 예 중 하나는 http://codebetter.com/matthewpodwysocki/2008/04/30/side-effecting-functions-are-code-smells/ 를 참조 하십시오 .

일반적으로 기본 원칙은 소프트웨어가 계층으로 구축되며 각 계층은 다음 계층에 대해 가장 깨끗한 추상화를 제공해야한다는 것입니다. 그리고 깨끗한 추상화는 가능한 한 적게 사용하여 사용해야하는 것입니다. 이를 모듈성이라고하며 단일 기능에서 네트워크 프로토콜에 이르기까지 모든 것에 적용됩니다.


OP가 "예상되는 부작용"이라고 묘사 한 것을 특징으로합니다. 예를 들어, 대리인은 목록의 각 요소에서 작동하는 일종의 엔진에 전달할 수 있습니다. 그것이 기본적으로하는 일 ForEach<T>입니다.
Robert Harvey

@RobertHarvey 이름이 적절하지 않은 메소드와 부작용을 알아 내기 위해 코드를 읽어야하는 것에 대한 불만은 부작용을 확실히 예상하지 못하게합니다.
btilly

나는 당신에게 그것을 부여합니다. 그러나 결과적으로 사이트 효과가 예상되는 적절한 이름의 문서화 된 방법은 결국 반 패턴이 아닐 수 있습니다.
Robert Harvey

@RobertHarvey 동의합니다. 중요한 부작용은 알아야 할 중요한 부작용이며, 신중하게 문서화해야한다는 것입니다 (바람직하게 분석법 이름으로).
btilly

나는 그것이 예기치 않은 부작용과 명백하지 않은 부작용의 혼합이라고 말합니다. 링크 주셔서 감사합니다.
Michiel van Oosterhout

3

우선, 이것은 "참조에 의한 통과"에 의존하지 않으며, 변경 가능한 참조 유형 인 객체에 의존합니다. 비 기능 언어에서는 거의 항상 그렇습니다.

둘째, 이것이 문제인지 아닌지는 객체와 다른 절차의 변경 사항이 얼마나 밀접하게 연결되어 있는지에 달려 있습니다 .Foo를 변경하지 못하고 Bar가 충돌하면 문제가됩니다. 코드 냄새가 반드시 나을 필요는 없지만 Foo 또는 Bar 또는 무언가에 문제가 있습니다 (입력을 확인해야하기 때문에 Bar 일 가능성이 있지만 잘못된 상태로 들어가는 것을 막을 수 있습니다).

나는 그것이 안티 패턴의 수준으로 상승한다고 말하고 싶지만 오히려 알아야 할 것이 있습니다.


2

A.Do(Something)수정 somethingsomething.Do()수정 사이에는 약간의 차이가 있다고 주장 합니다 something. 의 경우는 호출 된 메소드의 이름에서 명확해야한다고 something수정됩니다. something매개 변수 인지 this또는 환경의 일부 인지에 관계없이 메소드 이름에서 명확 하지 않은 경우 수정 해서는 안됩니다 .


1

일부 시나리오에서는 객체의 상태를 변경하는 것이 좋습니다. 예를 들어 사용자 목록이 있으며 클라이언트에 반환하기 전에 목록에 다른 필터를 적용하고 싶습니다.

var users = Dependency.Resolve<IGetUsersQuery>().GetAll();

var excludeAdminUsersFilter = new ExcludeAdminUsersFilter();
var filterByAnotherCriteria = new AnotherCriteriaFilter();

excludeAdminUsersFilter.Apply(users);
filterByAnotherCriteria.Apply(users); 

그리고 예, 필터링을 다른 방법으로 이동하여이를 예쁘게 만들 수 있으므로 다음과 같은 줄에 무언가가 생깁니다.

var users = Dependency.Resolve<IGetUsersQuery>().GetAll();
Filter(users);

어디 Filter(users)필터 위의 실행 것입니다.

이전에 정확히 어디에서 왔는지 기억이 나지 않지만 필터링 파이프 라인이라고합니다.


0

제안 된 새로운 솔루션 (객체 복사)이 패턴인지 확실하지 않습니다. 당신이 지적했듯이 문제는 기능의 나쁜 명명법입니다.

복잡한 수학 연산을 함수 f () 로 작성한다고 가정 해 봅시다 . 나는 그 문서 F () 매핑하는 함수 NXN로는 N, 그리고 그 뒤에 알고리즘을. 함수의 이름이 부적절하고 문서화되지 않았으며 테스트 사례가 없으며 코드를 이해해야하는 경우 코드가 쓸모가 없습니다.

솔루션에 대한 몇 가지 관찰 사항 :

  • 응용 프로그램은 다양한 측면에서 설계되었습니다. 객체가 값을 보유하는 데만 사용되거나 구성 요소 경계를 통과하는 경우 객체의 내부를 변경하는 방법에 대한 세부 사항으로 채우지 않고 외부로 변경하는 것이 좋습니다.
  • 객체를 복제하면 메모리 요구가 부풀어 오르고, 많은 경우 호환되지 않는 상태 ( 이후 가되었지만 실제로는 다름 )와 일시적인 불일치 X가 있는 등가의 객체가 존재 합니다.Yf()XY

해결하려는 문제는 유효한 문제입니다. 그러나 막대한 오버 엔지니어링을 수행하더라도 문제는 해결되지 않고 우회됩니다.


2
관찰 결과를 OP의 질문과 관련 시키면 더 나은 답변이 될 것입니다. 있는 그대로, 답변보다 더 많은 의견입니다.
Robert Harvey

1
@RobertHarvey +1, 좋은 관찰, 나는 그것을 편집 할 것입니다.
CMR
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.