단일 (공용) 메소드 만있는 클래스가 문제입니까?


51

현재 비디오 감시 영상에서 압축 및 색인 생성을 수행하는 소프트웨어 프로젝트를 진행 중입니다. 압축은 배경 및 전경 개체를 분할 한 다음 배경을 정적 이미지로 저장하고 전경을 스프라이트로 저장하여 작동합니다.

최근에 저는 프로젝트를 위해 디자인 한 클래스 중 일부를 검토하기 시작했습니다.

공용 메소드가 하나 뿐인 많은 클래스가 있음을 알았습니다. 이 수업 중 일부는 다음과 같습니다.

  • VideoCompressor ( compress유형의 입력 비디오를 가져 와서 유형 RawVideo의 출력 비디오를 반환하는 메서드 사용 CompressedVideo)
  • VideoSplitter ( split유형의 입력 비디오를 가져 와서 RawVideo각 유형의 출력 비디오 2 개로 구성된 벡터를 반환 하는 메서드 사용 RawVideo)
  • VideoIndexer ( index유형의 입력 비디오를 가져 와서 유형 RawVideo의 비디오 인덱스를 반환 하는 메서드 사용 VideoIndex)

나는 나 자신을 그냥 같은 통화를 할 수 각 클래스의 인스턴스를 찾을 수 있습니다 VideoCompressor.compress(...), VideoSplitter.split(...), VideoIndexer.index(...).

표면적으로 클래스 이름은 의도 된 기능을 충분히 설명하고 있으며 실제로는 명사라고 생각합니다. 이에 따라, 그들의 방법도 동사입니다.

이것이 실제로 문제입니까?


14
언어에 따라 다릅니다. C ++ 또는 Python과 같은 다중 패러다임 언어에서 이러한 클래스에는 비즈니스가 없습니다. "메서드"는 무료 함수 여야합니다.

3
@delnan : C ++에서도 전체 "OO 기능"이 필요하지 않더라도 일반적으로 모듈을 만들기 위해 클래스를 사용합니다. 실제로, "자유로운 기능"을 모듈로 그룹화하기 위해 네임 스페이스를 사용하는 대안이 있지만, 그 점에서 이점이 없습니다.
Doc Brown

5
@DocBrown : C ++에는 네임 스페이스가 있습니다. 사실 현대 C ++에서는 모든 인수에 대해 (정적으로) 오버로드 될 수 있지만 메소드는 호출자에 대해서만 오버로드 될 수 있기 때문에 메소드에 무료 함수를 자주 사용합니다.
Jan Hudec

2
@DocBrown : 문제의 핵심입니다. "외부"방법이 하나 뿐인 네임 스페이스를 사용하는 모듈은 함수가 충분히 복잡 할 때 완벽하게 좋습니다. 네임 스페이스는 어떤 것도 나타내는 척하지 않으며 객체 지향적 인 척하지 않기 때문입니다. 클래스는 그렇지 않지만 이와 같은 클래스는 실제로 네임 스페이스 일뿐입니다. 물론 Java에서는 함수를 가질 수 없으므로 결과입니다. 자바에 부끄러움.
Jan Hudec

2
관련 (거의 복제본) : programmers.stackexchange.com/q/175070/33843
Heinzi

답변:


87

아니, 이것은 문제가 아니며, 그 반대입니다. 그것은 모듈성의 표시이며 클래스의 명확한 책임입니다. 희박한 인터페이스는 해당 클래스의 사용자 관점에서 파악하기 쉽고 느슨한 결합을 권장합니다. 이것은 많은 장점이 있지만 단점은 거의 없습니다. 더 많은 구성 요소가 그런 식으로 설계되기를 바랍니다!


6
이것이 정답이며, 나는 그것을 찬성했지만 몇 가지 장점을 설명함으로써 더 잘 만들 수 있습니다. OP의 본능이 그에게 문제라고 말하는 것은 다른 것입니다. 즉 VideoCompressor는 실제로 인터페이스입니다. Mp4VideoCompressor는 클래스이며 VideoSplitter의 코드를 새 클래스로 복사하지 않고 다른 VideoCompressor와 교환 할 수 있어야합니다.
pdr

1
미안하지만 틀렸어 단일 메소드가있는 클래스는 독립형 함수 여야합니다.
Miles Rout

3
@MilesRout : 귀하의 의견은 귀하가 질문을 잘못 이해했음을 보여줍니다-OP는 하나의 방법이 아닌 하나의 공개 방법으로 클래스에 대해 썼습니다 (실제로 그는 하나의 의견으로 우리에게 말한 것처럼 여러 개인 메소드가있는 클래스를 의미했습니다).
Doc Brown

2
"단일 메소드를 가진 클래스는 독립형 함수 여야합니다." 그 메소드가 무엇인지에 대한 총체적인 가정. 하나의 공개 메소드가있는 클래스가 있습니다. 비하인드 IT는 해당 클래스의 여러 메소드와 클라이언트에 대해 전혀 모르는 5 개의 다른 클래스입니다. SRP를 훌륭하게 적용하면 계층 클래스 구조의 깔끔하고 간단한 인터페이스를 얻을 수 있습니다.
radarbob

2
@ 앤디 : 문제는 "이것은 문제입니까"이며 "이것은 특정 OO 정의를 준수하지 않습니다"입니다. 또한 OP가 상태가없는 클래스를 의미한다는 의문은 전혀 없습니다.
Doc Brown

26

더 이상 객체 지향이 아닙니다. 이러한 클래스는 아무 것도 나타내지 않기 때문에 함수의 그릇 일뿐입니다.

그렇다고 잘못되었다는 의미는 아닙니다. 기능이 충분히 복잡하거나 일반적인 경우 (즉, 인수가 구체적인 최종 유형이 아닌 인터페이스 인 경우) 해당 기능을 별도의 모듈 에 넣는 것이 좋습니다 .

거기에서 그것은 당신의 언어에 달려 있습니다. 언어에 무료 기능이있는 경우 기능을 내보내는 모듈이어야합니다. 왜 수업이 아닌 척하는 이유. 언어에 Java와 같은 무료 기능이없는 경우 단일 공용 메소드를 사용하여 클래스를 작성하십시오. 글쎄, 그것은 단지 객체 지향 디자인의 한계를 보여줍니다. 때로는 기능성이 단순히 더 잘 맞습니다.

단일 공용 메서드로 인터페이스를 구현해야하기 때문에 단일 공용 메서드가있는 클래스가 필요할 수 있습니다. 관찰자 패턴이나 의존성 주입 또는 무엇이든 사용하십시오. 다시 언어에 따라 다릅니다. 퍼스트 클래스 functor (C ++ ( std::function또는 템플릿 매개 변수), C # (delegate), Python, Perl, Ruby ( proc), Lisp, Haskell 등)가있는 언어에서 이러한 패턴은 함수 유형을 사용하며 클래스가 필요하지 않습니다. Java에는 아직 버전 8의 함수 유형이 없으므로 단일 메소드 인터페이스와 해당 단일 메소드 클래스를 사용합니다.

물론 하나의 거대한 함수 작성을 옹호하지는 않습니다. 개인 서브 루틴이 있어야하지만 구현 파일 (C ++의 파일 레벨 정적 또는 익명 네임 스페이스) 또는 공용 함수 내에서만 인스턴스화되는 개인 헬퍼 클래스 ( 데이터 저장 여부)에 대해 개인용 일 수 있습니다.


12
"왜 그렇지 않은 클래스 인 척 하는가?" 자유 함수 대신 객체를 사용하면 상태 및 하위 유형을 지정할 수 있습니다. 요구 사항이 변하는 세상에서는 가치가 있습니다. 조만간 비디오 압축 설정으로 재생하거나 대체 압축 알고리즘을 제공해야합니다. 그 전에 "클래스"라는 단어는이 기능이 쉽게 확장 가능하고 교체 가능한 소프트웨어 모듈에 속한다는 것을 알려줍니다. OO가 실제로 목표로 삼고 있지 않습니까?
COME

1
글쎄, 하나의 공개 방법 (및 보호 된 방법을 암시하는 종류) 만 가지고 있다면 실제로 확장 할 수 없습니다. 물론 압축 매개 변수를 압축하는 것이 의미가있을 수 있습니다.이 경우 함수 객체가됩니다 (일부 언어는 별도로 지원하지만 일부는 그렇지 않습니다).
Jan Hudec

3
이것은 객체와 함수 사이의 기본 연결입니다. 함수는 단일 메소드를 사용하고 필드가없는 객체와 동형입니다. 클로저는 단일 메소드와 일부 필드를 사용하여 객체와 동형입니다. 모두 같은 변수 집합에 가까운 여러 함수 중 하나를 반환하는 선택기 함수는 객체와 동형입니다. (실제로, 선택기 함수 대신 사전 데이터 구조를 사용하는 것을 제외하고는 객체가 JavaScript로 인코딩되는 방식입니다.)
Jörg W Mittag

4
"더 이상 객체 지향이 아니다.이 클래스는 아무것도 나타내지 않기 때문에 함수의 그릇 일 뿐이다." -이건 잘못이야 나는이 접근법이 더 객체 지향적이라고 주장한다. OO가 실제 객체를 나타내는 것은 아닙니다. 많은 양의 프로그래밍이 추상적 개념을 다루지 만,이를 해결하기 위해 OO 접근 방식을 적용 할 수 없습니까? 기필코 아니다. 모듈 성과 추상화에 관한 것입니다. 각 개체는 하나의 책임이 있습니다. 이를 통해 프로그램을 쉽게 둘러보고 변경할 수 있습니다. OOP의 수많은 이점을 나열 할 공간이 충분하지 않습니다.
Despertar

2
@Phoshi : 예, 이해합니다. 나는 기능적 접근 방식이 효과적이지 않다고 주장하지 않았다. 그러나 그것은 분명히 주제가 아닙니다. 비디오 컴프레서 또는 비디오 트랜스 모그리 파이어 또는 여전히 대상의 완벽한 후보입니다.
COME FROM

13

지정된 메소드를 전용 클래스로 추출해야하는 이유가있을 수 있습니다. 이러한 이유 중 하나는 Dependency Injection을 허용하는 것입니다.

VideoExporter결국 비디오를 압축 할 수 있는 클래스가 있다고 상상해보십시오 . 깨끗한 방법은 인터페이스를 갖는 것입니다.

interface IVideoCompressor
{
    Stream compress(Video video);
}

다음과 같이 구현됩니다.

class MpegVideoCompressor : IVideoCompressor
{
    // ...
}

class FlashVideoCompressor : IVideoCompressor
{
    // ...
}

그리고 이런 식으로 사용 :

class VideoExporter
{
    // ...
    void export(Destination destination, IVideoCompressor compressor)
    {
        // ...
        destination = compressor(this.source);
        // ...
    }
    // ...
}

나쁜 대안은해야하는 것 VideoExporter로터리 압축기의 압축을 포함한 모든 작업을, 공공 방법을 많이 가지고 있으며 않는합니다. 그것은 유지 보수의 악몽이되어 다른 비디오 형식에 대한 지원을 추가하기가 어려워졌습니다.


2
귀하의 답변은 공개 방법과 개인 방법을 구별하지 않습니다. 클래스의 모든 코드를 하나의 방법으로 만 사용하는 것이 좋습니다.
Doc Brown

2
@DocBrown : 비공개 메소드는이 질문과 관련이 없습니다. 그들은 내부 헬퍼 클래스 또는 무엇이든 놓을 수 있습니다.
Jan Hudec

2
@JanHudec : 현재 텍스트는 "나쁜 대안은 많은 방법을 가진 VideoExporter를 갖는 것"이지만 "나쁜 대안은 많은 공개 방법 을 가진 VideoExporter를 갖는 것 "입니다.
Doc Brown

@DocBrown : 여기에 동의하십시오.
Jan Hudec

2
@DocBrown : 귀하의 의견에 감사드립니다. 내 답변을 편집했습니다. 원래, 나는 그 질문 (그리고 나의 대답)이 공공 방법에 관한 것이라고 가정했습니다. 분명하지 않은 것 같습니다.
Arseni Mourzenko

6

이것은 함수를 다른 함수에 인수로 전달하려는 표시입니다. 귀하의 언어 (Java?)가 지원하지 않는 것 같습니다. 그럴 경우, 선택한 언어가 부족하기 때문에 디자인면에서 그다지 실패하지 않습니다. 이것은 모든 것이 클래스 여야한다고 주장하는 언어의 가장 큰 문제 중 하나입니다.

실제로 이러한 가짜 기능을 전달하지 않으면 자유 / 정적 기능이 필요합니다.


1
Java 8부터 람다가 있으므로 함수를 거의 전달할 수 있습니다.
Silviu Burcea

페어 포인트이지만 공식적으로 조금 더 오래 공개되지는 않으며 일부 직장은 최신 버전으로 천천히 이동합니다.
Doval

출시일은 3 월입니다. 또한, EAP 빌드는 JDK 8 : 매우 인기가 있었다
실비 Burcea

비록 Java 8 람다는 약간의 상용구 코드를 저장하는 것 외에 하나의 공용 메소드로만 객체를 정의하는 간단한 방법이지만, 여기서는 전혀 차이가 없습니다.
Jules

5

나는 파티에 늦었다는 것을 알고 있지만 모든 사람들이 이것을 지적하기 위해 놓친 것 같습니다.

이것은 Strategy Pattern 이라는 잘 알려진 디자인 패턴 입니다.

하위 패턴을 해결하기 위해 여러 가지 가능한 전략이있을 때 전략 패턴이 사용됩니다. 일반적으로 모든 구현에 대해 계약을 시행하는 인터페이스를 정의한 다음 특정 형태의 종속성 주입 을 사용하여 구체적인 전략을 제공합니다.

이 경우 예를 들어, 당신은 할 수 interface VideoCompressor후 예를 들어, 여러 다른 구현이 class H264Compressor implements VideoCompressorclass XVidCompressor implements VideoCompressor. OP가 없으면 인터페이스가 포함되어 있다고해도 명확하지는 않지만, 필요한 경우 전략 작성자가 전략 패턴을 구현하기 위해 문을 열어 두었을 수도 있습니다. 어느 쪽 자체도 좋은 디자인입니다.

OP가 지속적으로 메서드를 호출하기 위해 클래스를 인스턴스화하는 문제는 종속성 주입과 전략 패턴을 올바르게 사용하지 않는 문제입니다. 포함하는 클래스에는 필요한 곳에서 인스턴스화하는 대신 전략 개체가있는 멤버가 있어야합니다. 그리고이 멤버는 예를 들어 생성자에 주입되어야합니다.

대부분의 경우 전략 패턴은 단일 doStuff(...)메소드 만으로 인터페이스 클래스 (표시 한대로)를 생성 합니다.


1
public interface IVideoProcessor
{
   void Split();

   void Compress();

   void Index();
}

당신이 가진 것은 모듈 식이며 훌륭하지만, 이러한 책임을 IVideoProcessor로 그룹화한다면 DDD 관점에서 더 의미가 있습니다.

반면에 분할, 압축 및 인덱싱이 어떤 식 으로든 관련이 없다면 별도의 구성 요소로 유지하는 것보다 그렇습니다.


이러한 각 기능을 변경해야하는 이유가 다소 다르기 때문에 이러한 기능을 함께 사용하면 SRP를 위반한다고 주장합니다.
Jules

0

문제는 데이터가 아니라 디자인의 기능적 측면에서 작업하는 것입니다. 실제로 가지고있는 것은 OO 화 된 3 개의 독립형 기능입니다.

예를 들어 VideoCompressor 클래스가 있습니다. 비디오를 압축하도록 설계된 클래스로 작업하는 이유-이 유형의 각 객체에 포함 된 (비디오) 데이터를 압축하기위한 메소드가있는 Video 클래스가없는 이유는 무엇입니까?

OO 시스템을 설계 할 때 적용 할 수있는 활동을 나타내는 클래스가 아니라 객체를 나타내는 클래스를 만드는 것이 가장 좋습니다. 이전에는 클래스를 유형이라고했습니다. OO는 새로운 데이터 유형을 지원하여 언어를 확장하는 방법이었습니다. OO를 이와 같이 생각하면 클래스를 더 잘 디자인 할 수 있습니다.

편집하다:

concat 메소드가있는 문자열 클래스를 상상해보십시오. 클래스에서 인스턴스화 된 각 객체에 문자열 데이터가 포함 된 것을 구현할 수 있습니다.

string mystring("Hello"); 
mystring.concat("World");

그러나 OP는 다음과 같이 작동하기를 원합니다.

string mystring();
string result = mystring.concat("Hello", "World");

이제 클래스를 사용하여 관련 함수 모음을 보유 할 수있는 곳이 있지만 OO가 아닙니다. 코드베이스를 더 잘 관리하기 위해 언어의 OO 기능을 사용하는 편리한 방법이지만 어떤 방식도 아닙니다 "OO 디자인". 이러한 경우의 객체는 완전히 인공적이며 언어는 이러한 종류의 문제를 관리하는 데 더 좋은 것을 제공하지 않기 때문에 이와 같이 간단하게 사용됩니다. 예. C #과 같은 언어에서는 정적 클래스를 사용하여이 기능을 제공합니다. 클래스 메커니즘을 재사용하지만 더 이상 메소드를 호출하기 위해 오브젝트를 인스턴스화 할 필요는 없습니다. 당신은에 string.IsNullOrEmpty(mystring)비해 가난한 것 같은 방법으로 끝납니다 mystring.isNullOrEmpty().

따라서 누군가 "클래스를 어떻게 디자인합니까?"라고 물으면 클래스에 포함 된 함수보다는 클래스에 포함될 데이터를 생각하는 것이 좋습니다. "클래스는 클래스가 많다"고하면 "더 나은 C"스타일 코드를 작성하게됩니다. (C 코드를 개선하는 경우 반드시 나쁜 것은 아니지만) 최고의 OO 설계 프로그램을 제공하지는 않습니다.


9
-1, 전적으로 동의하지 않습니다. 초보자 OO 디자인은 a Video와 같은 데이터 객체에서만 작동 하며 기능으로 이러한 클래스를 과장하는 경향이 있습니다. 이러한 클래스는 종종 클래스 당> 10K LOC의 지저분한 코드로 끝납니다. 고급 OO 디자인은 기능을 a VideoCompressor와 같은 더 작은 단위로 세분화합니다 ( Video클래스를 데이터 클래스 또는 파사드로 만들 수 있습니다 VideoCompressor).
Doc Brown

5
@DocBrown는 다음 있기 때문에 어떤에서, 그러나 더 이상 객체 지향 디자인 지적하지 VideoCompressor나타내지 않는 개체를 . 아무 문제가 없습니다. 객체 지향 디자인의 한계를 보여줍니다.
Jan Hudec

3
아 그러나 1 개의 함수가 클래스로 바뀌는 초보자 OO는 실제로 OO가 아니고, 아무도 유지할 수없는 수천 개의 클래스 파일로 끝나는 대규모 디커플링 만 장려합니다. 클래스를 "기능 래퍼"가 아닌 "데이터 래퍼"로 생각하는 것이 가장 좋다고 생각합니다. 그러면 초보자는 OO 프로그램에 대해 생각하는 방법을 더 잘 이해할 수 있습니다.
gbjbaanb

4
@DocBrown은 실제로 내 관심사를 정확하게 강조했습니다. 나는 실제로 Video클래스를 가지고 있지만 압축 방법론은 결코 사소한 것이 아니므로 실제로 compress메소드를 여러 개의 다른 개인 메소드로 세분화합니다 . 이것은 동사 클래스를 만들지 마십시오
yjwong

2
나는 가지고 괜찮을 것 같아요 Video클래스를,하지만이 될 것입니다 구성 (A)의 VideoCompressor하는 VideoSplitter좋은 OO 형태로, 높은 응집력 개별 클래스해야하며, 기타 관련 클래스.
에릭 킹

-1

ISP (인터페이스 분리의 원칙)에는 클라이언트가 사용하지 않는 방법에 따라 강제되지 않는다는 것을 말한다. 이점은 여러 가지로 명확합니다. 귀하의 접근 방식은 ISP를 전적으로 존중하며 이는 좋습니다.

ISP와 관련하여 다른 접근 방식은 예를 들어 각 방법 (또는 높은 응집력을 가진 방법 세트)마다 인터페이스를 만든 다음 모든 인터페이스를 구현하는 단일 클래스를 갖는 것입니다. 이것이 더 나은 솔루션인지 아닌지는 시나리오에 달려 있습니다. 이것의 장점은 의존성 주입을 사용할 때 다른 공동 작업자를 가진 클라이언트 (각 인터페이스 당 하나씩)를 가질 수 있지만 결국 모든 공동 작업자는 동일한 객체 인스턴스를 가리킬 것입니다.

그건 그렇고, 당신은 말했다

나는 각 수업을 인스턴스화하는 것을 발견했다.

이 클래스는 서비스 (따라서 상태 비 저장) 인 것 같습니다. 그것들을 싱글 톤으로 만드는 것에 대해 생각해 보셨습니까?


1
싱글 톤은 일반적으로 반 패턴입니다. 1995 년 이후로 몰랐던 싱글 톤 : 문제 해결을 참조하십시오 .
Jan Hudec

3
나머지 인터페이스 분리 원칙은 좋은 패턴이지만이 질문은 인터페이스가 아닌 구현에 관한 것이므로 관련성이 확실하지 않습니다.
Jan Hudec

3
싱글 톤이 패턴인지 반 패턴인지는 논의하지 않겠습니다. 나는 그 문제에 대한 가능한 해결책 (이름조차도 있음)을 제안 했지만 그것이 맞는지 아닌지를 결정하는 것은 그에게 달려 있습니다.
diegomtassis

ISP의 관련성 여부와 관련하여 문제의 주제는 "단일 메서드 만있는 클래스가 문제입니까?"입니다. 그 반대는 많은 메서드가있는 클래스입니다. 클라이언트는 직접 의존합니다 ... Robert Martin의 제록스 예제와 같이 ISP를 공식화했습니다.
diegomtassis

-1

예, 문제가 있습니다. 그러나 심각하지는 않습니다. 나는 당신이 이런 식으로 코드를 구성 할 수 있고 나쁜 일이 발생하지 않는다는 것을 의미합니다. 그러나 이런 종류의 구조에는 약점이 있습니다. 예를 들어 비디오의 표현 (예 : RawVideo 클래스로 그룹화 된)이 변경되면 모든 작업 클래스를 업데이트해야합니다. 또는 런타임에 따라 다양한 비디오 표현이있을 수 있습니다. 그런 다음 표현을 특정 "작업"클래스와 일치시켜야합니다. 또한 주관적으로 smth에서 수행하려는 각 작업에 대한 종속성을 드래그하는 것은 성가신 일입니다. 작업이 더 이상 필요하지 않거나 새로운 작업이 필요하다고 결정할 때마다 전달되는 종속성 목록을 업데이트합니다.

또한 그것은 실제로 SRP 위반입니다. 일부 사람들은 SRP를 책임 분담을위한 지침으로 취급하고 (각 작업을 별개의 책임으로 취급하여 멀리까지 가져 가지만) SRP도 책임 분담을위한 지침임을 잊습니다. 그리고 SRP 책임에 따라 동일한 이유로 변경되는 변경 사항이 함께 그룹화되어야 변경이 발생할 경우 가능한 적은 클래스 / 모듈로 현지화됩니다. 큰 클래스의 경우, 해당 알고리즘이 관련되어있는 한 (즉, 해당 클래스 외부에서 알 수없는 지식을 공유하는 경우) 동일한 클래스에 여러 알고리즘을 갖는 것은 문제가되지 않습니다. 문제는 어떤 식 으로든 관련이 없으며 다른 이유로 변경 / 변경되는 알고리즘이있는 큰 클래스입니다.

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