단일 방법을 사용하는 클래스 – 최선의 접근 방법?


172

단일 기능을 수행하는 클래스가 있다고 가정 해보십시오. 기능을 수행 한 후에는 기능이 손상 될 수 있습니다. 이러한 접근 방법 중 하나를 선호 할 이유가 있습니까?

// Initialize arguments in constructor
MyClass myObject = new MyClass(arg1, arg2, arg3);
myObject.myMethod();

// Pass arguments to method
MyClass myObject = new MyClass();
myObject.myMethod(arg1, arg2, arg3);

// Pass arguments to static method
MyClass.myMethod(arg1, arg2, arg3);

다른 상황에 대한 지침을 얻으려고 의도적으로 세부 사항에 대해 모호했습니다. 그러나 Math.random ()과 같은 간단한 라이브러리 함수는 실제로 생각하지 않았습니다. 구체적이고 복잡한 작업을 수행하는 클래스를 더 많이 생각하고 있지만 하나의 (공용) 메소드 만 필요합니다.

답변:


264

정적 메소드로 채워진 유틸리티 클래스를 좋아했습니다. 그들은 다른 방법으로 중복 및 유지 관리 지옥을 초래할 수있는 도우미 방법을 크게 통합했습니다. 그것들은 사용하기 쉽고 인스턴스화도없고 폐기도 불필요합니다. 나는 이것이 서비스 지향 아키텍처를 만들기위한 첫 번째 의도하지 않은 시도라고 생각합니다. 그러나 시스템이 성장함에 따라 드래곤들이오고있다.

다형성
우리가 행복하게 울리는 UtilityClass.SomeMethod 메소드가 있다고 가정 해 봅시다. 갑자기 기능을 약간 변경해야합니다. 대부분의 기능은 동일하지만 그럼에도 불구하고 두 부분을 변경해야합니다. 정적 메서드가 아닌 경우 파생 클래스를 만들고 필요에 따라 메서드 내용을 변경할 수 있습니다. 정적 방법이므로 할 수 없습니다. 물론 이전 메소드 이전 또는 이후에 기능을 추가해야하는 경우 새 클래스를 작성하고 그 안에서 이전 클래스를 호출 할 수 있습니다.

인터페이스 문제
논리적 인 이유로 인터페이스를 통해 정적 메서드를 정의 할 수 없습니다. 정적 메서드를 재정의 할 수 없기 때문에 정적 클래스는 인터페이스에서 전달해야 할 때 쓸모가 없습니다. 이로 인해 전략 패턴의 일부로 정적 클래스를 사용할 수 없습니다. 인터페이스 대신 델리게이트를 전달 하여 일부 문제를 패치 할 수 있습니다 .

테스트
이것은 기본적으로 위에서 언급 한 인터페이스 문제와 밀접한 관련이 있습니다. 구현을 교환하는 능력이 매우 제한되어 있으므로 프로덕션 코드를 테스트 코드로 바꾸는 데 어려움이 있습니다. 다시 말하지만, 우리는 그것들을 마무리 할 수 ​​있지만 실제 객체 대신 래퍼를 허용 할 수 있도록 코드의 큰 부분을 변경해야합니다.

블로 브 육성
정적 메소드는 일반적으로 유틸리티 메소드로 사용되며 유틸리티 메소드는 일반적으로 다른 목적을 가지므로 비 일관성 기능으로 채워진 큰 클래스로 빠르게 끝나게됩니다. 이상적으로 각 클래스는 시스템 내에서 단일 목적을 가져야합니다 . 그들의 목적이 잘 정의되어 있다면 클래스의 5 배를 훨씬 더 갖고 싶습니다.

파라미터 크리프
우선, 그 귀엽고 순진한 정적 메서드에는 단일 매개 변수가 필요할 수 있습니다. 기능이 커지면 몇 가지 새로운 매개 변수가 추가됩니다. 선택적인 추가 매개 변수가 곧 추가되므로 메소드의 과부하를 작성하거나이를 지원하는 언어로 기본값을 추가합니다. 머지 않아 10 개의 매개 변수를받는 방법이 있습니다. 처음 세 개만 필요하며 매개 변수 4-7은 선택 사항입니다. 그러나 매개 변수 6을 지정하면 7-9도 채워야합니다 ...이 정적 메서드가 수행 한 작업을 수행하는 단일 목적으로 클래스를 만들었 으면 필요한 매개 변수를 생성자 및 사용자가 속성을 통해 선택적 값을 설정하거나 여러 개의 상호 종속 값을 동시에 설정하는 방법을 허용합니다. 또한 방법이이 정도의 복잡성으로 성장한 경우

아무 이유없이 소비자에게 클래스 인스턴스를 작성하도록 요구
가장 일반적인 주장 중 하나는 왜 우리 클래스의 소비자가이 단일 메소드를 호출하기 위해 인스턴스를 작성하고 나중에 인스턴스를 사용하지 않아도되는 인스턴스를 작성해야 하는가입니다. 클래스의 인스턴스를 만드는 것은 대부분의 언어에서 매우 저렴한 작업이므로 속도는 문제가되지 않습니다. 소비자에게 추가 코드 줄을 추가하는 것은 앞으로 훨씬 더 유지 보수가 쉬운 솔루션의 기초를 마련하는 데 드는 비용이 적습니다. 마지막으로, 인스턴스 생성을 피하려면 클래스의 싱글 톤 래퍼를 작성하여 쉽게 재사용 할 수 있습니다. 이렇게하면 클래스가 무국적이어야한다는 요구 사항이됩니다. 상태가없는 경우에도 모든 것을 처리하는 정적 래퍼 메서드를 만들면서도 장기적으로 모든 이점을 얻을 수 있습니다. 드디어,

Sith만이 절대적으로 다루는 것은
물론 정적 메소드를 싫어하는 것에는 예외가 있습니다. 부 풀릴 위험이없는 진정한 유틸리티 클래스는 정적 메소드 (System.Convert)의 훌륭한 사례입니다. 프로젝트가 향후 유지 보수에 대한 요구 사항이없는 일회성이라면 전체 아키텍처는 중요하지 않습니다. 정적이든 비 정적이든 실제로 중요하지 않습니다. 그러나 개발 속도는 중요합니다.

표준, 표준, 표준!
인스턴스 메소드를 사용해도 정적 메소드를 사용할 수 없으며 그 반대도 마찬가지입니다. 차별화의 이유가 있고 표준화되어있는 한. 다른 구현 방법으로 비즈니스 계층을 살펴 보는 것보다 나쁘지 않습니다.


Mark가 말했듯이, 다형성 (polymorphism), 메소드를 '전략'매개 변수로 전달할 수 있고 옵션을 구성 / 설정할 수있는 것은 모두 인스턴스 를 사용해야하는 이유 입니다. 예를 들면 다음과 같습니다. ListDelimiter 또는 DelimiterParser는 구문 구분 된 토큰에서 공백을 제거할지 여부, 목록을 괄호로 묶는 지 여부, 공백 / 널 목록을 처리하는 방법 등 사용 / 허용 구분 기호에 대해 구성 할 수 있습니다.
Thomas W

4
"시스템이 성장함에 따라 ..."리팩터링?
Rodney Gitzel

3
@ user3667089 클래스가 논리적으로 존재하는 데 필요한 일반 인수는 생성자에 전달합니다. 이들은 일반적으로 대부분 / 모든 방법에서 사용됩니다. 메소드 특정 인수는 해당 특정 메소드에 전달합니다.
마크 S. 라스무센

1
실제로 정적 클래스로 수행하는 작업은 객체 관리되지 않은 원시 C (메모리 관리이지만)로 돌아갑니다. 모든 함수는 전역 공간에 있으며 this전역 상태 는 없습니다 . 절차 적 프로그래밍이 마음에 드시면 이렇게하십시오. 그러나 OO의 많은 구조적 이점을 잃어버린 점에 감사하십시오.
엔지니어

1
이러한 이유의 대부분은 정적 메서드를 사용하는 것이 아니라 잘못된 코드 관리와 관련이 있습니다. F # 및 기타 기능적 언어에서는 기본적으로 정적 메소드 (인스턴스 상태가없는 함수)를 사용하며 이러한 문제가 없습니다. 즉, F #은 함수를 사용하기위한 더 나은 패러다임을 제공합니다 (함수는 일류이며, 함수는 커리 및 부분적으로 적용될 수 있음). 클래스를 사용하는 더 큰 이유는 그것이 C #을 위해 만들어 졌기 때문입니다. .NET Core의 모든 Dependency Injection 구성은 인스턴스 클래스를 중심으로 회전합니다.
저스틴 J 스탁

89

나는 정적 방식을 선호합니다. 클래스가 객체를 나타내지 않기 때문에 인스턴스를 만드는 것은 의미가 없습니다.

해당 메소드에만 존재하는 클래스는 정적으로 두어야합니다.


19
-1 "메소드에만 존재하는 클래스는 정적으로 남겨 두어야합니다."
Rookian

8
@ 루키 안 왜 동의하지 않습니까?
jjnguy

6
저장소 패턴이 그 예입니다. 클래스에는 메소드 만 포함됩니다. 귀하의 접근 방식에 따라 인터페이스를 사용할 수 없습니다. => 커플 링 증가
Rookian

1
@Rook, 일반적으로 사람들은 메소드에만 사용되는 클래스를 작성해서는 안됩니다. 이 예에서는 정적 메소드를 제공하는 것이 좋지 않습니다. 그러나 간단한 유틸리티 방법의 경우 정적 방식이 가장 좋습니다.
jjnguy

9
절대적으로 맞습니다 :) 당신의 조건으로 "간단한 유틸리티 방법"나는 당신에게 완전히 동의하지만, 당신은 당신의 답변에서 잘못된 imo라는 일반적인 규칙을 만들었습니다.
루키아

18

함수를 실행하기 위해 클래스의 인스턴스를 작성할 이유가없는 경우 정적 구현을 ​​사용하십시오. 이 클래스의 소비자가 필요하지 않은 인스턴스를 만드는 이유는 무엇입니까?


16

객체 의 상태 를 저장할 필요가 없으면 처음에 객체를 인스턴스화 할 필요가 없습니다. 매개 변수를 전달하는 단일 정적 메서드를 사용합니다.

또한 수십 개의 관련없는 정적 메소드가있는 거대한 Utils 클래스에 대해서도 경고합니다. 이것은 서두르면 혼란스럽고 다루기 힘들 수 있습니다. 관련 메소드가 거의없는 많은 클래스를 갖는 것이 좋습니다.


6

정적 메소드 형식이 더 나은 옵션이라고 말할 것입니다. 그리고 클래스를 정적으로 만들면 실수로 클래스 인스턴스를 만드는 것에 대해 걱정할 필요가 없습니다.


5

나는 실제로 상황이 무엇인지 알지 못하지만 arg1, arg2 또는 arg3이 속한 클래스 중 하나에 메소드로 넣는 것을 볼 것입니다-의미 적으로 클래스 중 하나가 방법.


4

제공된 정보를 바탕으로 답변하기가 어렵다고 제안합니다.

내 직감은 방금 하나의 방법을 사용하고 클래스를 즉시 버릴 경우 모든 매개 변수를 취하는 정적 클래스로 만드는 것입니다.

물론,이 하나의 메소드에 대해서만 단일 클래스를 작성해야하는 이유를 정확히 알기가 어렵습니다. 가장 가정하는 일반적인 "유틸리티 클래스"상황입니까? 아니면 미래에 더 많은 규칙 클래스를 구현하고 있습니까?

예를 들어, 해당 클래스를 플러그 가능하게하십시오. 그런 다음 하나의 메소드에 대한 인터페이스를 작성하고 모든 매개 변수를 생성자가 아닌 인터페이스로 전달하려고하지만 정적이기를 원하지 않습니다.


3

수업을 정적으로 할 수 있습니까?

그렇다면 모든 기능 클래스를 넣을 '유틸리티'클래스로 만들 것입니다.


2
다소 동의하지 않습니다. 내가 참여한 프로젝트에서 유틸리티 클래스에는 수백 개의 관련없는 메소드가있을 수 있습니다.
Paul Tomblin

3
@Paul : 일반적으로 동의했습니다. 수십 개 (또는 수백 개)의 완전히 관련이없는 메서드가있는 클래스를 보는 것이 싫어. 이 접근 방식을 취해야하는 경우, 이들을 더 작은 관련 유틸리티 세트 (EG, FooUtilities, BarUtilities 등)로 분할하십시오.
John Rudy

9
하나의 클래스 하나의 책임.
Andreas Petersson

3

이 메소드가 상태가없고 전달할 필요가없는 경우 정적으로 정의하는 것이 가장 좋습니다. 메소드를 전달해야하는 경우 다른 제안 된 접근 방식 중 하나 대신 델리게이트 사용을 고려할 수 있습니다 .


3

간단한 응용 프로그램과 internal도우미를 위해 정적 메서드를 사용합니다. 구성 요소가있는 응용 프로그램의 경우 Managed Extensibility Framework가 마음에 듭니다 . 다음은 API에서 찾을 수있는 패턴을 설명하기 위해 작성중인 문서에서 발췌 한 것입니다.

  • 서비스
    • I[ServiceName]Service인터페이스로 정의됩니다 .
    • 인터페이스 유형별로 내보내고 가져옵니다.
    • 단일 구현은 호스트 응용 프로그램에서 제공하며 내부 및 / 또는 확장에서 사용됩니다.
    • 서비스 인터페이스의 메소드는 스레드로부터 안전합니다.

고안된 예로서 :

public interface ISettingsService
{
    string ReadSetting(string name);

    void WriteSetting(string name, string value);
}

[Export]
public class ObjectRequiringSettings
{
    [Import]
    private ISettingsService SettingsService
    {
        get;
        set;
    }

    private void Foo()
    {
        if (SettingsService.ReadSetting("PerformFooAction") == bool.TrueString)
        {
            // whatever
        }
    }
}

2

나는 생성자에서 모든 것을 할 것입니다. 이렇게 :

new MyClass(arg1, arg2, arg3);// the constructor does everything.

또는

MyClass my_object(arg1, arg2, arg3);

MyClass 유형의 객체 이외의 것을 반환해야하는 경우 어떻게합니까?
Bill the Lizard

13
실행 논리를 생성자에 넣는 것은 나쁜 습관이라고 생각합니다. 좋은 개발은 의미론에 관한 것입니다. 의미 상, 다른 작업을 수행하지 않고 객체를 생성하기위한 생성자가 존재합니다.
mstrobl

bill, 반환 값이 필요한 경우 ret vvalue에 대한 참조를 전달하십시오. MyClass myObject (arg1, arg2, arg3, retvalue); mstrobl, 객체가 필요하지 않은 경우 왜 생성합니까? 이 트릭은 실제로 어떤 경우에는 도움이 될 수 있습니다.

객체에 상태가없는 경우 (즉, 변수가없는 경우) 인스턴스화하면 힙이나 스택에 할당이 생성 되지 않습니다 . C ++의 객체는 구조체를 가리키는 숨겨진 첫 번째 매개 변수를 사용하는 C 함수 호출로 생각할 수 있습니다.
mstrobl

mstrobl, 그것은 ctor가 사용할 수있는 개인 기능을 정의 할 수 있기 때문에 스테로이드의 ac 기능을 좋아합니다. 클래스 멤버 변수를 사용하여 개인 함수에 데이터를 전달할 수 있습니다.

0

고려해야 할 또 하나의 중요한 문제는 시스템이 다중 스레드 환경에서 실행되는지 여부와 정적 메서드 또는 변수를 갖는 것이 스레드 안전인지 여부입니다.

시스템 상태에주의해야합니다.


0

상황을 함께 피할 수 있습니다. 당신이 얻을 수 있도록 리팩토링을 시도하십시오 arg1.myMethod1(arg2, arg3). 더 적합한 경우 arg1을 arg2 또는 arg3으로 교체하십시오.

arg1의 클래스를 제어 할 수 없다면 장식하십시오 :

class Arg1Decorator
    private final T1 arg1;
    public Arg1Decorator(T1 arg1) {
        this.arg1 = arg1;
    }
    public T myMethod(T2 arg2, T3 arg3) {
        ...
    }
 }

 arg1d = new Arg1Decorator(arg1)
 arg1d.myMethod(arg2, arg3)

OOP에서 데이터를 처리하는 데이터와 메소드가 함께 속해 있기 때문입니다. 또한 Mark가 언급 한 모든 이점을 얻을 수 있습니다.


0

클래스 또는 클래스 인스턴스의 속성이 생성자 또는 메소드에서 사용되지 않으면 메소드는 '정적'패턴과 같이 디자인되지 않는 것이 좋습니다. 정적 메소드는 항상 '도움말'방식으로 생각해야합니다.


0

무언가 만하고 싶은지 아니면 돌려주고 싶은지에 따라 다음과 같이 할 수 있습니다.

public abstract class DoSomethingClass<T>
{
    protected abstract void doSomething(T arg1, T arg2, T arg3);
}

public abstract class ReturnSomethingClass<T, V>
{
    public T value;
    protected abstract void returnSomething(V arg1, V arg2, V arg3);
}

public class DoSomethingInt extends DoSomethingClass<Integer>
{
    public DoSomethingInt(int arg1, int arg2, int arg3)
    {
        doSomething(arg1, arg2, arg3);
    }

    @Override
    protected void doSomething(Integer arg1, Integer arg2, Integer arg3)
    {
        // ...
    }
}

public class ReturnSomethingString extends ReturnSomethingClass<String, Integer>
{
    public ReturnSomethingString(int arg1, int arg2, int arg3)
    {
        returnSomething(arg1, arg2, arg3);
    }

    @Override
    protected void returnSomething(Integer arg1, Integer arg2, Integer arg3)
    {
        String retValue;
        // ...
        value = retValue;
    }
}

public class MainClass
{
    static void main(String[] args)
    {
        int a = 3, b = 4, c = 5;

        Object dummy = new DoSomethingInt(a,b,c);  // doSomething was called, dummy is still around though
        String myReturn = (new ReturnSomethingString(a,b,c)).value; // returnSomething was called and immediately destroyed
    }
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.