수업의 실제 책임은 무엇입니까? just an

OOP에서 명사를 기반으로하는 동사를 사용하는 것이 합법적인지 궁금합니다.
기사 의 요점에 여전히 동의하지 않더라도이 훌륭한 기사를 발견 했습니다.

문제를 조금 더 설명하기 위해이 기사에서는 FileWriter클래스 가 없어야한다고 기술 하지만, 글쓰기는 액션 이므로 클래스 의 메소드 여야합니다 File. 루비 프로그래머가 FileWriter클래스를 사용하지 않을 가능성이 있기 때문에 (언제나 File.open파일에 액세스하는 방법 ), 자바 프로그래머는 그렇지 않기 때문에 언어에 의존한다는 것을 알게 될 것 입니다.

저의 개인적인 관점 (그리고 매우 겸손한)은 그렇게하면 단일 책임 원칙을 어기 게됩니다. PHP로 프로그래밍 할 때 (PHP는 OOP에 가장 적합한 언어이기 때문에 그렇지 않습니까?) 종종 이런 종류의 프레임 워크를 사용합니다.

<?php

// This is just an example that I just made on the fly, may contain errors

class User extends Record {

    protected $name;

    public function __construct($name) {
        $this->name = $name;
    }

}

class UserDataHandler extends DataHandler /* knows the pdo object */ {

    public function find($id) {
         $query = $this->db->prepare('SELECT ' . $this->getFields . ' FROM users WHERE id = :id');
         $query->bindParam(':id', $id, PDO::PARAM_INT);
         $query->setFetchMode( PDO::FETCH_CLASS, 'user');
         $query->execute();
         return $query->fetch( PDO::FETCH_CLASS );
    }


}

?>

접미사 DataHandler 가 관련을 추가하지 않는다는 것을 이해합니다 . 그러나 요점은 단일 책임 원칙에 따라 데이터를 포함하는 모델로 사용 된 개체 (레코드라고도 함)에 SQL 쿼리 및 데이터베이스 액세스를 수행 할 책임이 없어야합니다. 이것은 어떻게 든 Ruby on Rails에 의해 사용 된 ActionRecord 패턴을 무효화합니다.

며칠 전에이 C # 코드 (이 게시물에서 사용 된 네 번째 객체 언어)를 발견했습니다.

byte[] bytes = Encoding.Default.GetBytes(myString);
myString = Encoding.UTF8.GetString(bytes);

그리고 나는 클래스 Encoding또는 Charset클래스가 실제로 문자열을 인코딩 한다는 것이 의미가 없다고 말해야합니다 . 인코딩이 실제로 무엇인지를 나타내는 것일뿐입니다.

따라서 다음과 같이 생각하는 경향이 있습니다.

  • File파일을 열거 나 읽거나 저장 하는 것은 수업 책임 이 아닙니다 .
  • Xml직렬화 하는 것은 클래스 책임 이 아닙니다 .
  • User데이터베이스를 쿼리하는 것은 클래스 책임 이 아닙니다 .
  • 기타

우리는 이러한 아이디어를 추정하는 경우에는, 왜 것 ObjecttoString클래스를? 스스로를 문자열로 변환하는 것은 자동차 또는 개의 책임이 아닙니다.

실용적 관점에서 볼 toString때 엄격한 SOLID 형식을 따르는 아름다움을 제거하는 방법을 없애는 것은 쓸모없는 코드를 만들어서 코드를 유지 관리하는 것이 허용 가능한 옵션이 아니라는 것을 이해합니다.

또한 이에 대한 정확한 답변이 없을 수도 있고 ( 심각한 답변보다 에세이 일 수도 있음) 의견에 기반한 것일 수도 있습니다. 그럼에도 불구하고 내 접근 방식이 실제로 단일 책임 원칙이 무엇인지 따르는 지 알고 싶습니다.

수업의 책임은 무엇입니까?



답변

언어간에 차이가있을 경우 까다로운 주제가 될 수 있습니다. 따라서 저는 OO 영역 내에서 가능한 한 포괄적 인 방식으로 다음 주석을 작성하고 있습니다.

우선, 소위 “단일 책임 원칙”은 개념 응집력에 대한 반사 (명시 적으로 선언 된) 입니다. 당시의 문헌 (70 년 무렵)을 읽으면서 사람들은 모듈이 무엇인지 정의하고 멋진 속성을 보존하는 방식으로 구성하는 방법을 고심하고 있습니다. 그래서 그들은 “여기에 많은 구조와 절차가 있습니다. 모듈을 만들어 보겠습니다”라고 말하지만,이 임의의 것들이 함께 포장 된 이유에 대한 기준이 없으면 조직은 이해가되지 않을 수 있습니다. -작은 “응집력”. 따라서 기준에 대한 토론이 등장했다.

따라서 여기서 주목해야 할 첫 번째 사항은 지금까지 논쟁은 조직 및 유지 관리 및 이해에 대한 관련 영향에 관한 것입니다 (모듈이 “인식 된”경우 컴퓨터에 대한 문제는 거의 없음).

그런 다음, 다른 사람 (마틴 씨)이 수업에 참여해야 할 것과 그렇지 않아야 할 것에 대해 생각할 때 사용할 기준으로 같은 생각을 수업 단위에 적용하여이 기준을 원칙으로 승격시킵니다. 이리. 그가 한 요점은 “계급은 변화해야 할 단 하나의 이유가 있어야한다” 는 것이었다 .

우리는 경험을 통해 “많은 것들”을하는 것으로 보이는 많은 객체들 (그리고 많은 클래스들)이 그렇게하는 데 아주 좋은 이유가 있다는 것을 알고 있습니다. 바람직하지 않은 경우는 유지 보수 등을 할 수없는 시점까지 기능성으로 부풀어 오른 클래스가 될 것입니다. 후자를 이해하는 것은 mr. 마틴은이 주제에 대해 자세히 설명하고있었습니다.

물론, 씨 씨를 읽은 후 마틴은 “책임”이 잘못 정의되었을 때 강력한 규정 준수는 물론 모든 종류의 규정 준수를 추구하는 것이 아니라 문제가되는 시나리오 를 피하기 위한 방향과 설계의 기준이라는 점을 분명히해야합니다. 원칙을 어 기고 있는가? “는 광범위한 혼란의 완벽한 예입니다.) 따라서 안타깝게도 원칙 이라고합니다., 잘못 인도하는 사람들은 그것을하지 않는 마지막 결과로 가져 가려고합니다. 마틴은 스스로 분리하는 것이 더 나쁜 결과를 낳을 수 있기 때문에 아마도 그런 식으로 유지되어야하는 “하나 이상의 일을하는”디자인에 대해 논의했다. 또한 모듈 성과 관련하여 알려진 문제가 많으며 (이 주제는 이에 해당함), 간단한 질문에도 좋은 답변을 얻을 수있는 시점이 아닙니다.

그러나 이러한 아이디어를 외삽하면 Object에 왜 toString 클래스가 있습니까? 스스로를 문자열로 변환하는 것은 자동차 또는 개의 책임이 아닙니다.

이제 여기서 잠시 이야기를하겠습니다 toString. 모듈에서 클래스로 사고를 전환하고 클래스에 속하는 메소드를 반영 할 때 일반적으로 무시되는 기본 사항이 있습니다. 그리고 문제는 동적 디스패치 (일명 바인딩, “다형성”)입니다.

“재정의 메소드”가없는 세계에서 “obj.toString ()”또는 “toString (obj)”중 하나를 선택하는 것은 구문 기본 설정의 문제입니다. 그러나 프로그래머가 기존 / 재정의 된 메소드를 고유하게 구현하여 서브 클래스를 추가하여 프로그램의 동작을 변경할 수있는 세계에서는이 선택이 더 이상 맛이 없습니다. “무료 절차”(다중 방법을 지원하는 언어는 이분법을 벗어날 수있는 방법)에 대해서도 마찬가지입니다. 결과적으로 이는 더 이상 조직에 대한 논의가 아니라 의미론에 대한 논의입니다. 마지막으로, 어떤 클래스에 메소드가 바인딩되어 있는지도 영향을 미치는 결정이됩니다 (그리고 지금까지 우리는 사물이 어디에 속해 있는지 결정하는 데 도움이되는 지침을 거의 제공하지 않습니다.

마지막으로, 우리는 끔찍한 디자인 결정을 수행하는 언어에 직면합니다. 따라서, 한때 정식 적 이유와 대상 (그리고 따라서 학급에서 클래스)을 갖는 주된 기준은 무엇인가, 즉 “데이터처럼 행동하는 행동”의 일종 인 이러한 “대상”을 갖는 것입니다. 그러나 모든 비용에 대한 직접적인 조작 (구체적으로 클라이언트의 관점에서 객체의 인터페이스가되어야하는 주된 힌트)으로부터 구체적인 표현 (있는 경우)을 보호하고 흐리게 처리합니다.


답변

[참고 : 여기서는 물체에 대해 이야기하겠습니다. 객체는 객체 지향 프로그래밍이 결국 클래스가 아닌 것입니다.]

개체의 책임은 주로 도메인 모델에 따라 다릅니다. 일반적으로 동일한 도메인을 모델링하는 방법에는 여러 가지가 있으며 시스템 사용 방법에 따라 한 가지 방법을 선택합니다.

우리 모두 알다시피, 입문 과정에서 종종 배우는 “동사 / 명사”방법론은 어리석은 일입니다. 왜냐하면 문장을 구성하는 방법에 크게 의존하기 때문입니다. 능동적이거나 수동적 인 목소리로 거의 모든 것을 명사 나 동사로 표현할 수 있습니다. 도메인 모델이 그에 의존하는 것은 잘못된 것입니다. 요구 사항을 구성하는 방식의 우연한 결과가 아니라 어떤 것이 대상인지 방법인지에 대한 의식적인 디자인 선택이어야합니다.

그러나, 그것은 않는 영어에서 같은 일을 표현하는 많은 가능성을 가지고처럼이 보여, 당신은 당신의 도메인 모델에 같은 일을 표현하는 많은 가능성을 가지고 … 그 가능성 중 어느 것도 본질적으로 올바른 다른 사람보다 않습니다. 상황에 따라 다릅니다.

다음은 입문 OO 과정에서 매우 인기있는 예입니다 : 은행 계좌. 일반적으로 balance필드와 transfer메소드가 있는 객체로 모델링됩니다 . 다시 말해 계좌 잔고는 데이터 이고 이체는 작업 입니다. 은행 계좌를 모델링하는 합리적인 방법입니다.

그러나 실제 은행 소프트웨어에서 은행 계좌가 모델링되는 방식 은 아닙니다 (실제로 은행이 실제 은행에서 작동하는 방식이 아님). 대신 거래 전표가 있으며 계정 잔액은 계정에 대한 모든 거래 전표를 더하고 빼서 계산합니다. 다시 말해, 전송은 데이터 이고 잔액은 작업입니다 ! (흥미롭게도 이것은 잔액을 수정할 필요가 없기 때문에 계정 개체와 트랜잭션 개체를 변경할 필요가 없기 때문에 시스템을 순수하게 작동하게 만듭니다.)

에 대한 귀하의 특정 질문에 대해서는 toString동의합니다. 나는 Showable 타입 클래스 의 Haskell 솔루션을 매우 선호한다 . (Scala는 유형 클래스가 OO와 아름답게 어울린다고 알려줍니다.) 동일성. 평등은 매우 자주 하지 객체의하지만 객체가 사용되는 문맥의 속성입니다. 부동 소수점 숫자에 대해서만 생각하십시오. 엡실론은 무엇이어야합니까? Haskell은 Eq타입 클래스를 가지고 있습니다.


답변

너무 자주 단일 책임 원칙은 무책임 원칙이되고, 아무것도하지 않는 빈혈 클래스 (세터 및 게터 제외)가 생겨서 명사왕국 재앙으로 이어 집니다.

당신은 그것을 완벽하게 얻을 수는 없지만 수업이 너무 적은 것보다 조금 더하는 것이 좋습니다.

인코딩하면 예에서, IMO, 그것은 확실히 해야 인코딩 할 수 있습니다. 대신 무엇을해야합니까? “utf”라는 이름을 갖는 것은 책임이 없습니다. 아마도 이름은 Encoder 여야합니다. 그러나 Konrad가 말했듯이 데이터 (인코딩)와 동작 (연행)은 서로 속해 있습니다 .


답변

클래스의 인스턴스는 클로저입니다. 그게 다야. 그렇게 생각하면 잘 설계된 모든 소프트웨어가 올바르게 보일 것이며 잘 생각하지 못한 소프트웨어는 그렇지 않을 것입니다. 확장하겠습니다.

파일에 쓸 내용을 쓰려면 파일 이름, 액세스 방법 (읽기, 쓰기, 추가), 쓸 문자열과 같이 OS에 지정해야하는 모든 것을 생각하십시오.

따라서 파일 이름 (및 액세스 방법)으로 File 객체를 구성합니다. File 객체는 이제 파일 이름 위에 닫힙니다 (이 경우 아마도 readonly / const 값으로 받아 들일 것입니다). File 인스턴스는 이제 클래스에 정의 된 “write”메소드를 호출 할 준비가되었습니다. 이것은 단지 문자열 인수를 취하지 만, write 메소드의 본문에서 구현은 파일 이름 (또는 그로부터 작성된 파일 핸들)에 대한 액세스도 있습니다.

따라서 File 클래스의 인스턴스는 일부 데이터를 일종의 복합 blob으로 묶어서 나중에 해당 인스턴스를 사용하는 것이 더 간단합니다. File 객체가 있으면 파일 이름이 무엇인지 걱정할 필요가 없으며 쓰기 호출에 인수로 넣은 문자열 만 걱정됩니다. 다시 말하면 File 객체는 작성하려는 문자열이 실제로 어디로 가고 있는지 알 필요가없는 모든 것을 캡슐화합니다.

해결하려는 대부분의 문제는 이러한 방식으로 계층화됩니다. 앞에서 생성 된 것, 프로세스 루프의 각 반복이 시작될 때 생성 된 것, if의 두 반쪽에 선언 된 것, 그런 다음 생성 된 것 어떤 종류의 서브 루프의 시작, 그 루프 내에서 알고리즘의 일부로 선언 된 것들 등. 스택에 점점 더 많은 함수 호출을 추가함에 따라 더 세밀한 코드를 수행하고 있지만 매번 레이어의 데이터를 스택의 하위 계층에 저장하여 액세스하기 쉽게 만듭니다. 기능적 프로그래밍 방식을위한 일종의 클로저 관리 프레임 워크로 “OO”를 사용하고 있는지 확인하십시오.

일부 문제에 대한 바로 가기 솔루션은 가변 상태의 객체 상태와 관련이 있습니다.이 기능은 작동하지 않습니다. 당신은 클래스의 “글로벌”에서 적어도 위의 범위에있는 것들을 만들고 있습니다. 세터를 작성할 때마다 피하십시오. 나는 이것이 당신이 당신의 수업의 책임 또는 그것이 운영되는 다른 수업의 책임을 얻은 곳이라고 주장합니다. 그러나 너무 걱정하지 마십시오-때로는 약간의 변경 가능한 상태로 저 어서 너무 많은인지 부하없이 특정 종류의 문제를 신속하게 해결하고 실제로 무언가를 수행하는 것이 실용적입니다.

요약하면, 질문에 답하기 위해-수업의 실제 책임은 무엇입니까? 좀 더 자세한 종류의 작업을 수행하려면 일부 기본 데이터를 기반으로 일종의 플랫폼을 제시해야합니다. 데이터의 다른 절반보다 덜 자주 변경되는 작업과 관련된 데이터의 절반을 캡슐화해야합니다 (카레 잉 생각 …). 예를 들어 파일 이름 대 쓸 문자열.

종종 이러한 캡슐화는 피상적으로 실제 객체 / 문제 영역의 객체처럼 보이지만 속지 않습니다. Car 클래스를 만들 때 실제로는 자동차가 아니라 자동차에서하고 싶은 특정 종류의 작업을 수행 할 수있는 플랫폼을 형성하는 데이터 모음입니다. 문자열 표현 (toString)을 구성하는 것은 내부 데이터를 모두 뱉어내는 것 중 하나입니다. 문제 영역이 모두 자동차에 관한 것이더라도 때때로 자동차 클래스가 올바른 클래스가 아닐 수도 있습니다. 명사 왕국과 달리, 수업의 근간이되는 연산, 동사입니다.


답변

또한 이에 대한 정확한 답변이 없을 수도 있고 (심각한 답변보다 에세이 일 수도 있음) 의견에 기반한 것일 수도 있습니다. 그럼에도 불구하고 내 접근 방식이 실제로 단일 책임 원칙이 무엇인지 따르는 지 알고 싶습니다.

그렇더라도 반드시 좋은 코드를 만드는 것은 아닙니다. 어떤 종류의 맹목적으로 따르는 규칙이 잘못된 코드로 이어진다는 단순한 사실 외에도, (프로그래밍) 객체는 (물리적) 객체가 아닙니다.

객체는 일련의 응집력있는 데이터 및 작업 모음이며 때로는 두 가지 중 하나 일뿐입니다. 이 종종 모델 실제 객체이지만, 무언가의 컴퓨터 모델과 그 일 자체의 차이는 필요로 차이를.

사물을 나타내는 “명사”와이를 소비하는 다른 사물 사이의 단단한 선을 취함으로써 객체 지향 프로그래밍의 주요 이점 (함수와 상태를 함께 가져서 함수가 상태의 불변을 보호 할 수 있음)에 반대하게됩니다. . 더 나쁜 것은, 역사에서 보여준 것처럼 물리적 세계를 코드로 표현하려고 시도하는 것입니다.


답변

며칠 전에이 C # 코드 (이 게시물에서 사용 된 네 번째 객체 언어)를 발견했습니다.

byte[] bytes = Encoding.Default.GetBytes(myString);
myString = Encoding.UTF8.GetString(bytes);

그리고 나는 클래스 Encoding또는 Charset클래스가 실제로 문자열을 인코딩 한다는 것이 의미가 없다고 말해야합니다
. 인코딩이 실제로 무엇인지를 나타내는 것일뿐입니다.

이론적으로는 그렇습니다. 그러나 C #은 단순성을 위해 정당한 타협을한다고 생각합니다 (자바의보다 엄격한 접근 방식과 반대).

경우 Encoding특정 인코딩 표현 (또는 확인) – 말을, UTF-8 – 그럼 당신은 분명히 필요할 것 Encoder구현할 수 있도록도 클래스를 GetBytes그것에 -하지만 다음의 관계 관리 할 필요가 Encoders와 Encoding우리는 그래서들 우리의 좋은 ol로 끝내

EncodersFactory.getDefaultFactory().createEncoder(new UTF8Encoding()).getBytes(myString)

당신이 링크 한 기사에서 훌륭하게 숭배되었습니다.

내가 당신을 잘 이해한다면, 당신은 선이 어디에 있는지 묻습니다.

스펙트럼의 반대편에는 절차 적 접근 방식이 있으며 정적 클래스를 사용하여 OOP 언어로 구현하기는 어렵지 않습니다.

HelpfulStuff.GetUTF8Bytes(myString)

그것에 대한 큰 문제는 HelpfulStuff나뭇잎 의 저자가 모니터 앞에 앉아 있을 때 GetUTF8Bytes구현 된 위치를 알 수있는 방법이 없다는 것입니다 .

나는 그것을 찾아 마십시오 HelpfulStuff, Utils, StringHelper?

그 방법이 실제로 세 가지 모두에서 독립적으로 구현된다면 주님은 저를 도와주십시오 … 그러나 그들은 또 다른 하나를 snap 고 이제 우리는 그것들을 풍부하게 가지고 있습니다.

나에게, 적절한 디자인은 추상적 기준을 만족시키는 것이 아니라 코드 앞에 앉아서 수행하기가 쉽다는 것입니다. 이제 어떻게

EncodersFactory.getDefaultFactory().createEncoder(new UTF8Encoding()).getBytes(myString)

이 측면에서 평가? 나쁘다. 동료에게 “문자열을 바이트 시퀀스로 어떻게 인코딩합니까?”라고 외치면 듣고 싶은 대답이 아닙니다. 🙂

그래서 나는 적당한 접근 방식으로 가고 단일 책임에 대해 자유로울 것입니다. 오히려 Encoding인코딩 유형을 식별하고 실제 인코딩을 수행 하는 클래스를 사용 하고 싶습니다 . 동작과 분리되지 않은 데이터.

나는 그것이 기어에 그리스를 바르는 데 도움이되는 정면 패턴으로 통과한다고 생각합니다. 이 합법적 인 패턴이 SOLID를 위반합니까? 관련 질문 : Facade 패턴이 SRP를 위반합니까?

이것은 인코딩 된 바이트를 얻는 것이 내부적으로 (.NET 라이브러리에서) 구현되는 방법 일 수 있습니다. 클래스는 공개적으로 표시되지 않으며이 “외관”API 만 외부에 노출됩니다. 그것이 사실인지 확인하는 것은 어렵지 않습니다. 지금 당장 너무 게으르다 🙂 아마 아닐 수도 있지만, 내 요점을 무효화하지는 않습니다.

내부적으로보다 가혹한 표준 구현을 유지하면서 친숙 함을 위해 공개적으로 보이는 구현을 단순화하도록 선택할 수 있습니다.


답변

Java에서 파일을 나타내는 데 사용되는 추상화는 스트림이며 일부 스트림은 단방향 (읽기 전용 또는 쓰기 전용)이며 다른 스트림은 양방향입니다. Ruby의 경우 File 클래스는 OS 파일을 나타내는 추상화입니다. 따라서 질문은 단일 책임이 무엇입니까? Java에서 FileWriter의 책임은 OS 파일에 연결된 단방향 바이트 스트림을 제공하는 것입니다. Ruby에서 File의 책임은 시스템 파일에 대한 양방향 액세스를 제공하는 것입니다. 그들은 모두 SRP를 수행하고 서로 다른 책임을 가지고 있습니다.

스스로를 문자열로 변환하는 것은 자동차 또는 개의 책임이 아닙니다.

왜 안돼? Car 클래스가 Car 추상화를 완전히 나타낼 것으로 기대합니다. 문자열이 필요한 곳에 자동차 추상화가 사용되는 것이 합리적이라면 Car 클래스가 문자열로의 변환을 지원할 것으로 기대합니다.

더 큰 질문에 직접 대답하려면 클래스의 책임은 추상화 역할을하는 것입니다. 그 작업은 복잡 할 수 있지만, 일종의 요점입니다. 추상화는 복잡한 것을 사용하기 쉽고 덜 복잡한 인터페이스 뒤에 숨겨야합니다. SRP는 설계 지침서이므로 “이 추상화는 무엇을 나타내는가?”라는 질문에 중점을 둡니다. 개념을 완전히 추상화하기 위해 여러 가지 다른 동작이 필요한 경우에도 마찬가지입니다.