일반적으로 객체 또는 해당 멤버 변수를 함수로 보냅니 까?


31

이 두 경우 사이에 일반적으로 허용되는 방법은 다음과 같습니다.

function insertIntoDatabase(Account account, Otherthing thing) {
    database.insertMethod(account.getId(), thing.getId(), thing.getSomeValue());
}

또는

function insertIntoDatabase(long accountId, long thingId, double someValue) {
    database.insertMethod(accountId, thingId, someValue);
}

다시 말해서 일반적으로 전체 객체를 전달하거나 필요한 필드 만 전달하는 것이 더 낫습니까?


5
그것은 함수의 목적과 문제의 대상과의 관계 (또는 관련이없는)에 전적으로 달려 있습니다.
MetaFight

그게 문제입니다. 어느 쪽을 사용할 것인지 알 수 없습니다. 나는 항상 접근 방식에 맞게 코드를 변경할 수 있다고 생각합니다.
AJJ

1
API 용어에서 (구현을 전혀 보지 않음) 전자는 추상적이고 도메인 지향적이며 (좋은) 후자는 그렇지 않습니다 (나쁜).
Erik Eidt

1
첫 번째 방법은 더 많은 3 계층 OO입니다. 그러나 메소드에서 단어 데이터베이스를 제거하여 훨씬 더 커야합니다. "Store"또는 "Persist"여야하며 Account 또는 Thing을 수행해야합니다 (둘다는 아님). 이 계층의 클라이언트는 저장 매체를 인식하지 않아야합니다. 계정을 검색 할 때는 원하는 객체를 식별하기 위해 id 또는 필드 값이 아닌 속성 값의 조합을 전달해야합니다. 또는 모든 계정을 전달하는 열거 방법이 중요하지 않습니다.
Martin Maat

1
일반적으로 둘 다 잘못되었거나 최적이 아닙니다. 객체를 데이터베이스에 직렬화 하는 방법 은 일반적으로 객체의 멤버 변수에 직접적으로 의존하기 때문에 객체의 속성 (멤버 함수)이어야합니다. 개체의 멤버를 변경하는 경우 직렬화 방법도 변경해야합니다. 객체의 일부인 경우 더 효과적입니다.
tofro

답변:


24

일반적으로 다른 것보다 낫지는 않습니다. 사례별로 판단해야합니다.

그러나 실제로는 실제로이 결정을 내릴 수있는 위치에있을 때 전체 프로그램 아키텍처에서 어떤 계층이 오브젝트를 기본 요소로 분리해야하는지 결정해야하기 때문에 전체 호출에 대해 생각해야합니다. stack , 당신이 현재하고있는이 방법뿐만 아니라 어쩌면 해체는 어딘가에서 이루어져야하며 아마도 두 번 이상 수행하는 것은 의미가 없습니다 (또는 불필요하게 오류가 발생하기 쉬울 것입니다). 문제는 그 장소가 어디에 있어야 하는가입니다.

이 결정을 내리는 가장 쉬운 방법 은 객체가 변경 될 때 어떤 코드를 변경해야하는지 또는 변경하지 말아야 하는지를 생각하는 입니다. 예제를 약간 확장 해 봅시다 :

function addWidgetButtonClicked(clickEvent) {
    // get form data
    // get user's account
    insertIntoDatabase(account, data);
}
function insertIntoDatabase(Account account, Otherthing data) {
    // open database connection
    // check data doesn't already exist
    database.insertMethod(account.getId(), data.getId(), data.getSomeValue());
}

vs

function addWidgetButtonClicked(clickEvent) {
    // get form data
    // get user's account
    insertIntoDatabase(account.getId(), data.getId(), data.getSomeValue());
}
function insertIntoDatabase(long accountId, long dataId, double someValue) {
    // open database connection
    // check data doesn't already exist
    database.insertMethod(accountId, dataId, someValue);
}

첫 번째 버전에서 UI 코드는 맹목적으로 data객체를 전달 하고 유용한 코드를 추출하는 데이터베이스 코드에 달려 있습니다. 두 번째 버전에서 UI 코드는 data객체를 유용한 필드로 나누고 데이터베이스 코드는 객체의 출처를 모르면서 직접받습니다. 중요한 의미는 객체 의 구조가 data어떤 식 으로든 변경되는 경우 첫 번째 버전은 데이터베이스 코드 만 변경하고 두 번째 버전은 UI 코드 만 변경하면된다는 것 입니다. 이 두 가지 중 올바른 것은 data개체 에 어떤 종류의 데이터가 포함되어 있는지에 달려 있지만 일반적으로 매우 분명합니다. 예를 들어data"20/05/1999"와 같은 사용자 제공 문자열이며, Date전달하기 전에 UI 유형 에 따라 올바른 유형 으로 변환해야합니다 .


8

전체 목록은 아니지만 객체를 메소드로 인수로 전달해야하는지 여부를 결정할 때 다음 요소 중 일부를 고려하십시오.

객체가 불변인가? 기능이 '순수'입니까?

부작용 은 코드 유지 관리를위한 중요한 고려 사항입니다. 변경 가능한 상태 저장 객체가 많은 코드가 모든 곳에서 전달되는 경우 전역 코드가 직관적이지 않은 방식으로 코드가 직관적이지 않고 디버깅이 더 어려워지고 시간이 많이 걸립니다. 태워 버리는.

경험상 가능한 한 메소드에 전달하는 모든 객체가 불변이되도록 보장하는 것이 좋습니다.

함수 호출의 결과로 인수의 상태가 변경 될 것으로 예상되는 디자인을 피하십시오 (적절하게 가능한 한).이 접근법에 대한 가장 강력한 논거 중 하나 는 최소한의 놀라움원리입니다 . 즉, 누군가 코드를 읽고 함수에 전달 된 인수를 보는 것은 함수가 반환 된 후 상태가 변경 될 것으로 예상 할 가능성이 적습니다.

이 메소드에는 이미 몇 개의 인수가 있습니까?

인수 목록이 너무 긴 메소드 (대부분의 인수에 '기본'값이 있더라도)는 코드 냄새처럼 보이기 시작합니다. 그러나 때때로 그러한 함수가 필요할 수 있으며 Parameter Object 처럼 행동하는 것이 유일한 목적인 클래스를 만드는 것을 고려할 수 있습니다 .

이 방법에는 '소스'객체에서 매개 변수 객체로 적은 양의 추가 상용구 코드 매핑이 포함될 수 있지만 성능과 복잡성 측면에서 비용이 많이 들지만 디커플링 측면에서 많은 이점이 있습니다. 및 객체 불변성.

전달 된 개체가 응용 프로그램 내 "레이어"(예 : ViewModel 또는 ORM 엔터티)에만 속합니까?

우려 분리 (SoC) 에 대해 생각해보십시오 . 메소드가 존재하는 동일한 계층 또는 모듈 (예 : 핸드 롤 API 랩퍼 라이브러리 또는 핵심 비즈니스 로직 계층 등)에 오브젝트가 속하는지 여부를 스스로에게 물어 보면 해당 오브젝트를 실제로 전달해야하는지 여부를 알 수 있습니다. 방법.

SoC는 깨끗하고 느슨하게 결합 된 모듈 식 코드를 작성하기위한 좋은 기반입니다. 예를 들어 ORM 엔터티 개체 (코드와 데이터베이스 스키마 간 매핑)는 비즈니스 계층에서 전달되거나 프레젠테이션 / UI 계층에서 더 이상 전달되지 않아야합니다.

'레이어'간에 데이터를 전달하는 경우 일반적으로 메서드에 전달 된 일반 데이터 매개 변수를 갖는 것이 '잘못된'레이어에서 오브젝트를 전달하는 것보다 바람직합니다. '올바른'레이어에 존재하는 별도의 모델을 사용하는 것이 좋습니다.

함수 자체가 너무 크거나 복잡합니까?

함수에 많은 데이터 항목이 필요한 경우 해당 함수가 너무 많은 책임을 맡고 있는지 고려해 볼 가치가 있습니다. 더 작은 물체와 더 짧고 간단한 기능을 사용하여 리팩토링 할 수있는 기회를 찾으십시오.

함수가 명령 / 쿼리 객체 여야합니까?

어떤 경우에는 데이터와 기능 간의 관계가 밀접 할 수 있습니다. 이러한 경우 명령 오브젝트 또는 조회 오브젝트 가 적절한 지 고려하십시오.

메소드에 객체 매개 변수를 추가하면 포함 클래스가 새로운 종속성을 강제로 적용합니까?

"Plain old data"인수에 대한 가장 강력한 주장은 수신 클래스가 이미 깔끔하게 포함되어 있고 메소드 중 하나에 오브젝트 매개 변수를 추가하면 클래스가 오염된다는 것입니다 (또는 클래스가 이미 오염 된 경우). 기존 엔트로피를 악화시킨다)

완전한 객체를 실제로 전달해야합니까, 아니면 해당 객체 인터페이스의 작은 부분 만 필요합니까?

함수와 관련 하여 인터페이스 분리 원리 를 고려하십시오. 즉, 객체를 전달할 때는 실제로 해당 인수의 인터페이스 부분 (기능)에 의존해야합니다.


5

따라서 함수를 만들 때 코드를 호출하는 계약을 암시 적으로 선언합니다. "이 기능은이 정보를 가져 와서 다른 것으로 바꾼다 (아마 부작용이있을 수있다)".

그래서, 계약은 논리적으로, 또는 필드 오브젝트 (그러나 그들이 구현하고)와 함께해야 너무 일 이 다른 개체의 일부가 될 수 있습니다. 어느 쪽이든 커플 링을 추가하지만 프로그래머는 그것이 어디에 속하는지를 결정해야합니다.

일반적으로 명확하지 않은 경우 함수가 작동하는 데 필요한 가장 작은 데이터를 선호하십시오. 함수는 객체에서 찾은 다른 것들을 필요로하지 않기 때문에 종종 필드 만 전달하는 것을 의미합니다. 그러나 때로는 전체 사물을 가져가는 것이 더 정확합니다. 미래에 상황이 필연적으로 변할 때 영향이 적기 때문입니다.


왜 메소드 이름을 insertAccountIntoDatabase로 변경하지 않습니까? 아니면 다른 유형을 전달 하시겠습니까 ?. obj를 사용할 특정 인수 수에서 코드를 쉽게 읽을 수 있습니다. 귀하의 경우 방법 이름이 어떻게 할 것인지 대신 삽입 할 내용이 명확하다고 생각합니다.
Laiv

3

따라 다릅니다.

좀 더 정교하게 설명하자면, 당신의 메소드가 받아들이는 매개 변수는 당신이하려는 것과 의미 적으로 일치해야합니다. 메소드 EmailInviter의 다음 세 가지 가능한 구현을 고려하십시오 invite.

void invite(String emailAddressString) {
  invite(EmailAddress.parse(emailAddressString));
}
void invite(EmailAddress emailAddress) {
  ...
}
void invite(User user) {
  invite(user.getEmailAddress());
}

모든 문자열이 이메일 주소가 아니기 때문에 String전달해야 할 위치를 전달하는 EmailAddress데 결함이 있습니다. EmailAddress클래스는 더 의미 론적 방법의 동작을 일치합니다. 그러나 A의 통과는 User왜 지구에가가해야하기 때문에 또한 결함이 EmailInviter사용자를 초대로 제한? 사업은 어떻습니까? 파일 또는 명령 줄에서 이메일 주소를 읽는데 사용자와 연결되지 않은 경우 어떻게됩니까? 메일 링리스트? 목록은 계속됩니다.

여기에 안내를 위해 사용할 수있는 몇 가지 경고 신호가 있습니다. 모든 문자열이나 int 와 같은 단순한 값 유형을 사용 String하거나 int모든 문자열이나 int가 유효하지 않거나 그것에 대해 "특별한"것이있는 경우보다 의미있는 유형을 사용해야합니다. 객체를 사용하고 있고 getter를 호출하는 것이 유일한 방법이라면 getter의 객체를 직접 전달해야합니다. 이 가이드 라인은 어렵거나 빠르지 않지만 가이드 라인은 거의 없습니다.


0

Clean Code는 가능한 한 적은 인수를 사용하도록 권장합니다. 즉, Object가 일반적으로 더 나은 접근 방식이며 의미가 있다고 생각합니다. 때문에

insertIntoDatabase(new Account(id) , new Otherthing(id, "Value"));

보다 읽기 쉬운 전화입니다

insertIntoDatabase(myAccount.getId(), myOtherthing.getId(), myOtherthing.getValue() );

거기에 동의 할 수 없습니다. 둘은 동의어가 아닙니다. 메소드에 전달하기 위해 2 개의 새 오브젝트 인스턴스를 작성하는 것은 좋지 않습니다. 옵션 중 하나 대신 insertIntoDatabase (myAccount, myOtherthing)를 사용합니다.
jwenting

0

구성 상태가 아닌 객체를 통과시킵니다. 이는 캡슐화 및 데이터 숨기기의 객체 지향 원칙을 지원합니다. 필요하지 않은 다양한 메소드 인터페이스에서 객체의 내부를 노출하는 것은 핵심 OOP 원칙을 위반합니다.

에서 필드를 변경하면 어떻게됩니까 Otherthing? 유형을 변경하거나 필드를 추가하거나 필드를 제거 할 수 있습니다. 이제 질문에 언급 한 것과 같은 모든 방법을 업데이트해야합니다. 객체를 통과하면 인터페이스가 변경되지 않습니다.

객체에서 필드를 허용하는 메소드를 작성해야하는 유일한 경우는 객체를 검색하는 메소드를 작성할 때입니다.

public User getUser(String primaryKey) {
  return ...;
}

해당 호출을 수행 할 때 해당 메소드를 호출하는 지점이 오브젝트를 가져 오기 때문에 호출 코드에 아직 오브젝트에 대한 참조가 없습니다.


1
"에서 필드를 변경하면 어떻게됩니까 Otherthing?" (1) 공개 / 폐쇄 원칙을 위반하는 것입니다. (2) 전체 객체를 전달하더라도 그 안의 코드는 해당 객체의 멤버에 액세스하고 (그렇지 않은 경우 왜 객체를 전달합니까?) 여전히 중단 될 것입니다.
David Arno

@DavidArno 내 대답의 요점은 아무것도 깨지지 않을 것이 아니라 깨지는 다는 입니다 . 첫 번째 단락도 잊지 마십시오. 어떤 부분이 깨지 든 객체의 내부 상태는 객체의 인터페이스를 사용하여 추상화되어야합니다. 내부 상태를 통과하는 것은 OOP 원칙을 위반하는 것입니다.

0

유지 관리 성 관점에서 보면 인수는 컴파일러 수준에서 서로 명확하게 구분할 수 있어야합니다.

// this has exactly one way to call it
insertIntoDatabase(Account ..., Otherthing ...)

// the parameter order can be confused in practice
insertIntoDatabase(long ..., long ...)

첫 번째 디자인은 버그를 조기에 감지합니다. 두 번째 디자인은 테스트에 표시되지 않는 미묘한 런타임 문제로 이어질 수 있습니다. 따라서 첫 번째 디자인이 선호됩니다.


0

둘 중 제 선호는 첫 번째 방법입니다.

function insertIntoDatabase(Account account, Otherthing thing) { database.insertMethod(account.getId(), thing.getId(), thing.getSomeValue()); }

변경 사항이 해당 게터를 유지하여 변경 사항이 객체 외부에서 투명하게 유지되는 한 변경 사항이 길 아래로 객체를 변경했기 때문에 변경 및 테스트 할 코드가 적고 앱을 방해 할 가능성이 적습니다.

이것은 제가 생각하는 과정입니다. 주로이 자연의 것들을 어떻게 작업하고 구조화하고 장기적으로 관리 및 유지 관리가 가능한지에 기초합니다.

명명 규칙에 들어가지는 않겠지 만이 방법에는 "데이터베이스"라는 단어가 있지만 그 저장 메커니즘이 변경 될 수 있음을 지적합니다. 표시된 코드에서, 사용중인 데이터베이스 스토리지 플랫폼 또는 데이터베이스 인 경우에도 기능을 연관시키는 것은 없습니다. 이름에 있기 때문에 방금 가정했습니다. 다시 말하지만, 해당 게터가 항상 보존된다고 가정하면 이러한 객체가 저장되는 방법 / 위치를 쉽게 변경할 수 있습니다.

두 객체 구조, 특히 사용되는 게터에 종속되는 함수가 있기 때문에 함수와 두 객체를 다시 생각할 것입니다. 또한이 함수는이 두 개체를 하나의 누적 항목으로 묶어 지속되는 것처럼 보입니다. 내 직감은 세 번째 물건이 의미가 있다고 말해줍니다. 이 객체들과 이들이 실제로 어떻게 예상 로드맵과 관련되어 있는지에 대해 더 알아야합니다. 그러나 내 직감은 그 방향으로 기울고 있습니다.

코드가 지금 서있는 것처럼, 질문은 "이 기능은 어디에 또는 어디에 있어야합니까?" 계정 또는 기타 사항의 일부입니까? 어디로 갑니까?

세 번째 개체 "데이터베이스"가 이미 있다고 생각 하고이 기능을 해당 개체에 넣는 데 기울고 있습니다. 그러면 계정과 기타를 처리하고 결과를 유지할 수있는 개체 작업이됩니다. .

세 번째 객체가 ORM (Object-Relational Mapping) 패턴을 준수하도록 만드는 것이 더 좋습니다. 따라서 코드를 사용하는 모든 사람에게 "아, 계정과 기타 정보가 서로 뭉개 져서 지속되는 곳"이라는 것을 이해하게 될 것입니다.

그러나 Account와 OtherThing을 결합하고 변환하는 작업을 처리하지만 지속 메커니즘을 처리하지 않는 네 번째 객체를 도입하는 것도 의미가 있습니다. 이 두 객체와의 상호 작용이 더 많을 것으로 예상되는 경우 지속성 비트 만 지속성 만 관리하는 객체로 고려하기를 원합니다.

Account, OtherThing 또는 세 번째 ORM 개체 중 하나를 다른 세 개도 변경하지 않고도 변경할 수 있도록 디자인을 유지하려고 노력했습니다. 합당한 이유가 없다면, Account와 OtherThing이 독립적이기를 원하고 서로의 내부 활동과 구조를 알 필요가 없습니다.

물론 이것이 될 것이라는 전체 맥락을 알고 있다면 내 아이디어를 완전히 바꿀 수 있습니다. 다시, 이것은 내가 이런 것을 볼 때 어떻게 생각하는지, 어떻게 마른 지에 대한 것입니다.


0

두 가지 방법 모두 장단점이 있습니다. 시나리오에서 더 나은 것은 사용 사례에 따라 다릅니다.


Pro Multiple params, Con Object 참조 :

  • 호출자 가 특정 클래스에 바인딩되지 않았으므로 다른 소스의 값을 모두 전달할 수 있습니다.
  • 메소드 실행 내 에서 오브젝트 상태가 예기치 않게 수정되는 것이 안전 합니다.

프로 오브젝트 참조 :

  • 메소드가 Object 참조 유형에 바인딩되어 있음을 명확하게 인터페이스 하여 실수로 관련되지 않은 / 잘못된 값을 전달 하는 것을 어렵게 함
  • 필드 / 게터 이름을 바꾸 려면 구현뿐만 아니라 메소드를 호출 할 때마다 변경해야합니다.
  • 경우 새 속성을 추가 하고 요구 사항이 전달 될, 변경은 메소드 서명 필요하지
  • 방법은 할 수 있습니다 객체 상태를
  • 비슷한 기본 유형의 변수를 너무 많이 전달 하면 호출자와 순서가 혼동됩니다. (빌더 패턴 문제).

따라서 무엇이 사용되어야하고 언제 사용 사례에 따라 달라지는가

  1. 개별 매개 변수 전달 : 일반적으로 방법에 오브젝트 유형과 관련이없는 경우 더 많은 사용자에게 적용 할 수 있도록 개별 매개 변수 목록 을 전달하는 것이 좋습니다 .
  2. 새로운 모델 객체를 소개합니다 : 매개 변수 목록이 3 이상으로 커지면 호출 된 API에 속한 새 모델 객체를 도입하는 것이 좋습니다 (빌더 패턴 선호)
  3. Pass Object Reference : 메소드가 도메인 오브젝트와 관련이있는 경우, 오브젝트 참조를 전달하는 것이 유지 보수성과 가독성 관점에서 더 좋습니다.

0

한쪽에는 계정 및 기타 개체가 있습니다. 반면에, 계정 ID와 기타 ID가 주어지면 데이터베이스에 값을 삽입 할 수 있습니다. 그것이 주어진 두 가지입니다.

Account and Otherthing을 인수로 사용하는 메소드를 작성할 수 있습니다. 프로 측에서는 발신자가 계정 및 기타 정보에 대해 자세히 알 필요가 없습니다. 부정적인 측면에서, 수신자는 계정 및 기타 방법에 대해 알아야합니다. 또한 계정에 객체 ID가 있지만 객체 자체가 아닌 경우 다른 객체 값보다 데이터베이스에 다른 것을 삽입하는 방법이 없으며이 방법을 사용할 수 없습니다.

또는 두 개의 ID와 값을 인수로 사용하는 메소드를 작성할 수 있습니다. 부정적인 측면에서, 발신자는 계정 및 기타 정보를 알아야합니다. 그리고 실제로 데이터베이스에 삽입하기 위해 ID보다 계정 또는 기타에 대한 자세한 정보가 필요한 상황이있을 수 있습니다.이 경우이 솔루션은 전혀 쓸모가 없습니다. 반면에, 수신자에게는 계정 및 기타 정보가 필요하지 않으며 더 많은 유연성이 있기를 바랍니다.

판단 요청 : 유연성이 더 필요합니까? 이것은 종종 한 번의 호출의 문제가 아니지만 모든 소프트웨어에서 일관됩니다. 대부분의 계정 ID를 사용하거나 개체를 사용합니다. 그것을 혼합하면 두 세계의 최악을 얻을 수 있습니다.

C ++에서는 두 개의 id와 값을 갖는 메소드와 Account 및 Otherthing을 취하는 인라인 메소드를 사용할 수 있으므로 오버 헤드가없는 두 가지 방법이 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.