이 아키텍처로 OOP 연습을 중단하고 있습니까?


23

웹 애플리케이션이 있습니다. 나는 기술이 중요하다고 생각하지 않습니다. 구조는 왼쪽 이미지에 표시된 N 계층 응용 프로그램입니다. 3 개의 레이어가 있습니다.

UI (MVC 패턴), BLL (Business Logic Layer) 및 DAL (Data Access Layer)

내가 가진 문제는 응용 프로그램 이벤트 호출을 통한 논리와 경로가 있기 때문에 BLL이 방대하다는 것입니다.

응용 프로그램을 통한 일반적인 흐름은 다음과 같습니다.

UI에서 발생한 이벤트, BLL의 메소드로 이동하고 논리 (BLL의 여러 부분에서)를 수행하고 결국 DAL에서 BLL (더 많은 논리)로 돌아가서 UI에 일부 값을 리턴합니다.

이 예제의 BLL은 매우 바빠서 이것을 어떻게 나누는 지 생각하고 있습니다. 나는 또한 내가 싫어하는 논리와 객체를 결합했습니다.

여기에 이미지 설명을 입력하십시오

오른쪽 버전은 나의 노력입니다.

논리는 여전히 응용 프로그램이 UI와 DAL간에 흐르는 방식이지만 속성은 없을 것입니다 ... 메서드 만 (이 계층의 대부분의 클래스는 상태를 저장하지 않으므로 정적 일 있음). Poco 레이어는 속성이있는 클래스가 존재하는 곳입니다 (예 : 이름, 나이, 키 등이있는 Person 클래스). 이것들은 응용 프로그램의 흐름과 관련이 없으며 상태 만 저장합니다.

흐름은 다음과 같습니다.

UI에서 트리거되고 일부 데이터를 UI 계층 컨트롤러 (MVC)에 전달합니다. 이는 원시 데이터를 변환하여 poco 모델로 변환합니다. 그런 다음 poco 모델은 논리 계층 (BLL)으로 전달되고 결국에는 명령 쿼리 계층으로 전달되어 잠재적으로 도중에 조작됩니다. 명령 쿼리 계층은 POCO를 데이터베이스 개체 (거의 같은 것이지만 하나는 지속성을 위해 설계되고 다른 하나는 프런트 엔드를 위해 설계됨)로 변환합니다. 항목이 저장되고 데이터베이스 개체가 명령 쿼리 계층으로 반환됩니다. 그런 다음 POCO로 변환되어 로직 레이어로 돌아와 잠재적으로 더 처리 한 다음 마지막으로 UI로 돌아갑니다.

공유 로직 및 인터페이스는 MaxNumberOf_X 및 TotalAllowed_X 및 모든 인터페이스와 같은 지속적 데이터를 보유 할 수있는 곳입니다.

공유 로직 / 인터페이스와 DAL은 모두 아키텍처의 "기본"입니다. 이들은 외부 세계에 대해 아무것도 모른다.

공유 로직 / 인터페이스 및 DAL 이외의 모든 포코에 대해 알고 있습니다.

흐름은 여전히 ​​첫 번째 예제와 매우 유사하지만 각 레이어가 하나의 상태 (흐름, 흐름 또는 다른 것)를 더 책임지게합니다 ...하지만이 접근법으로 OOP를 깨고 있습니까?

Logic과 Poco를 시연하는 예는 다음과 같습니다.

public class LogicClass
{
    private ICommandQueryObject cmdQuery;
    public PocoA Method1(PocoB pocoB) 
    { 
        return cmdQuery.Save(pocoB); 
    }

    /*This has no state objects, only ways to communicate with other 
    layers such as the cmdQuery. Everything else is just function 
    calls to allow flow via the program */
    public PocoA Method2(PocoB pocoB) 
    {         
        pocoB.UpdateState("world"); 
        return Method1(pocoB);
    }

}

public struct PocoX
{
     public string DataA {get;set;}
     public int DataB {get;set;}
     public int DataC {get;set;}

    /*This simply returns something that is part of this class. 
     Everything is self-contained to this class. It doesn't call 
     trying to directly communicate with databases etc*/
     public int GetValue()
     {

         return DataB * DataC; 
     }

     /*This simply sets something that is part of this class. 
     Everything is self-contained to this class. 
     It doesn't call trying to directly communicate with databases etc*/
     public void UpdateState(string input)
     {        
         DataA += input;  
     }
}

현재 설명했듯이 아키텍처에 근본적으로 잘못된 것이 없습니다.
Robert Harvey

19
더 많은 통찰력을 제공하기 위해 코드 예제에 기능적인 세부 사항이 충분하지 않습니다. 풋바 예제는 거의 충분하지 않습니다.
Robert Harvey

1
고려 사항 제출 : Baruco 2012 : Gary Bernhardt
Theraot

4
이 때문에 우리는이 질문에 대한 더 나은 제목을 찾을 수 있습니다 온라인으로 쉽게 찾을 수?
Soner Gönül

1
pedantic하기 위해서만 : 티어와 레이어는 같은 것이 아닙니다. "계층"은 배포, 논리에 대한 "계층"에 대해 말합니다. 데이터 계층은 서버 측 코드 및 데이터베이스 계층 모두에 배포됩니다. UI 계층은 웹 클라이언트 및 서버 측 코드 계층 모두에 배포됩니다. 표시하는 아키텍처는 3 계층 아키텍처입니다. 계층은 "웹 클라이언트", "서버 측 코드"및 "데이터베이스"입니다.
Laurent LA RIZZA

답변:


54

예, 핵심 OOP 개념을 깨뜨릴 가능성이 큽니다 . 그러나 나쁘게 느끼지 마십시오. 사람들이 항상이 작업을 수행한다고해서 아키텍처가 "잘못된"것은 아닙니다. 적절한 OO 디자인보다 유지 관리가 쉽지 않을 수도 있지만 이것은 다소 주관적이며 귀하의 질문은 아닙니다. ( 여기서는 n 계층 아키텍처를 일반적으로 비판하는 기사가 있습니다).

추론 : OOP의 가장 기본적인 개념은 데이터와 논리가 단일 단위 (객체)를 형성한다는 것입니다. 이것은 매우 간단하고 기계적 설명이지만, 실제로 디자인에서 따르지 않습니다 (정확히 이해하면). 대부분의 데이터를 대부분의 논리에서 명확하게 분리하고 있습니다. 예를 들어 상태 비 저장 (정적 유사) 방법을 사용하는 것을 "프로 시저"라고하며 일반적으로 OOP와는 대조적입니다.

물론 예외는 있지만,이 디자인은 일반적으로 이러한 사항을 위반합니다.

다시 한 번, "OOP 위반"! = "잘못된"을 강조하고 싶기 때문에 이것이 반드시 가치 판단 일 필요는 없습니다. 그것은 모두 아키텍처 제약 조건, 유지 보수성 유스 케이스, 요구 사항 등에 달려 있습니다


9
Ovo 코드를 작성하지 않고 자신을 발견하면 OOP가 아닌 언어를 고려해야합니다. 사용하지 않으면 할 수있는 많은 추가 오버 헤드가 있습니다.
TheCatWhisperer

2
@TheCatWhisperer : 최신 엔터프라이즈 아키텍처는 선택적으로 (예 : DTO의 경우) OOP를 완전히 버리지 않습니다.
Robert Harvey

@RobertHarvey 동의합니다. 디자인의 어느 곳에서도 OOP를 거의 사용하지 않는다면 의미는 없습니다.
TheCatWhisperer

@TheCatWhisperer c #과 같은 oop의 많은 장점은 반드시 언어의 oop 부분이 아니라 라이브러리, Visual Studio, 메모리 관리 등과 같은 지원에 있습니다.

@Orangesandlemons 나는 다른 잘 지원되는 언어가 많이 있다고 확신합니다 ...
TheCatWhisperer

31

함수형 프로그래밍의 핵심 원칙 중 하나는 순수한 함수입니다.

객체 지향 프로그래밍의 핵심 원칙 중 하나는 기능을 수행하는 데이터와 함께 기능을 배치하는 것입니다.

이러한 핵심 원칙은 응용 프로그램이 외부 세계와 통신해야 할 때 사라집니다. 실제로 시스템의 특수하게 준비된 공간에서만 이러한 이상에 충실 할 수 있습니다. 모든 코드 줄이 이러한 이상을 충족해야하는 것은 아닙니다. 그러나 코드 라인이 이러한 이상을 충족하지 않으면 실제로 OOP 또는 FP를 사용한다고 주장 할 수 없습니다.

따라서 관심있는 코드를 이동하기 위해 리팩토링 할 수없는 경계를 넘어 데이터를 가져와야하는 데이터 "객체"만 있으면됩니다. OOP가 아님을 아십시오. 그게 현실이야 OOP는 해당 경계 내에서 일단 해당 데이터에 작용하는 모든 논리를 한 곳에 모을 때입니다.

당신도 그렇게 할 필요는 없습니다. OOP가 모든 사람들에게 필요한 것은 아닙니다. 그것이 무엇입니까. OOP가 그렇지 않거나 코드를 유지하려는 사람들을 혼란스럽게 할 때 OOP를 따르는 것을 주장하지 마십시오.

귀하의 POCO는 비즈니스 로직을 가지고있는 것처럼 보이므로 빈혈에 대해 너무 걱정하지 않아도됩니다. 나를 걱정하는 것은 모두 매우 변하기 쉬워 보인다는 것입니다. 게터와 세터는 실제 캡슐화를 제공하지 않습니다. POCO가 해당 경계를 향하고 있다면 괜찮습니다. 이것이 실제 캡슐화 된 OOP 객체의 모든 이점을 제공하지 않는다는 것을 이해하십시오. 일부는이를 데이터 전송 객체 또는 DTO라고합니다.

내가 성공적으로 사용한 트릭은 DTO를 먹는 OOP 객체를 만드는 것입니다. 나는 DTO를 매개 변수 객체 로 사용한다 . 내 생성자는 상태에서 ( 방어 적 사본 으로 읽음) 상태를 읽고 옆으로 치 웁니다. 이제 완전히 캡슐화되고 변경할 수없는 DTO 버전이 있습니다. 이 데이터와 관련된 모든 방법은 해당 경계의 측면에있는 경우 여기로 이동할 수 있습니다.

게터 나 세터는 제공하지 않습니다. 나는 묻지 말고 따르십시오 . 당신은 내 방법을 부르고 그들은 필요한 일을합니다. 그들은 당신에게 그들이 한 일조차 말하지 않을 것입니다. 그들은 단지 그것을한다.

이제는 결국 어딘가에 다른 경계가 생겨나 고이 모든 것이 다시 분리됩니다. 괜찮아. 다른 DTO를 돌려 벽 위로 던지십시오.

이것이 포트 및 어댑터 아키텍처의 핵심입니다. 나는 기능적인 관점 에서 그것에 대해 읽고 있습니다. 아마 당신도 관심이있을 것입니다.


5
" 게터와 세터는 실제 캡슐화를 제공하지 않습니다 "-예!
거미 보리스

3
@BoristheSpider-게터와 세터는 캡슐화를 절대적으로 제공합니다. 캡슐화의 좁은 정의에 맞지 않습니다.
Davor Ždralo

4
@ DavorŽdralo : 때때로 해결 방법으로 유용하지만 그 특성상 게터와 세터가 캡슐화를 깨뜨립니다. 내부 변수를 가져오고 설정하는 방법을 제공하는 것은 자신의 상태를 책임지고 수행하는 것과 반대입니다.
cHao

5
@cHao-당신은 게터가 무엇인지 이해하지 못합니다. 객체 속성 값을 반환하는 메서드를 의미하지는 않습니다. 일반적인 구현이지만 데이터베이스에서 값을 반환하고 http를 통해 요청하고 즉시 계산할 수 있습니다. 내가 말했듯이, 게터와 세터는 사람들이 자신의 좁은 정의를 사용할 때만 캡슐화를 깨뜨립니다.
Davor Ždralo

4
@cHao-캡슐화는 구현을 숨기고 있음을 의미합니다. 그것이 캡슐화되는 것입니다. Square 클래스에 "getSurfaceArea ()"게터가있는 경우 표면 영역이 필드인지 여부를 알 수없는 경우 (즉, 반환 높이 * 너비) 또는 일부 세 번째 방법으로 계산되면 내부 구현을 변경할 수 있습니다 캡슐화되어 있기 때문에 언제든지
Davor Ždralo

1

설명을 올바르게 읽으면 객체가 다음과 같이 보입니다.

public class LogicClass
{
    private ICommandQueryObject cmdQuery;
    public PocoA Method(PocoB pocoB) { ... }
}

public class PocoX
{
     public string DataA {get;set;}
     public int DataB {get;set;}
     ... etc
}

Poco 클래스에는 데이터 만 포함되며 Logic 클래스에는 해당 데이터에 작용하는 메소드가 포함됩니다. 예, "Classic OOP"의 원칙을 어겼습니다.

다시 말하지만, 일반적인 설명에서 말하기는 어렵지만 작성한 내용이 빈혈 도메인 모델로 분류 될 수 있다는 위험이 있습니다.

나는 이것이 특히 나쁜 접근 방법이라고 생각하지 않으며, Poco를 구조체로 생각하면 OOP를 좀 더 구체적으로 의미하지 않습니다. 당신의 객체는 이제 LogicClasses입니다. 실제로 Pocos를 불변으로 만들면 디자인은 상당히 기능적인 것으로 간주 될 수 있습니다.

그러나 Shared Logic, Pocos는 동일하지만 정적이 아닌 Pocos를 참조하면 디자인의 세부 사항에 대해 걱정하기 시작합니다.


필자가 예제를 복사하여 내 게시물에 추가했습니다. 죄송합니다 ti 시작이 명확하지 않았습니다
MyDaftQuestions

1
내 말은 응용 프로그램의 기능을 알려 주면 예제를 작성하는 것이 더 쉽다는 것입니다. LogicClass 대신 PaymentProvider 또는 무엇이든 가질 수 있습니다
Ewan

1

내가 디자인에서 보았던 한 가지 잠재적 인 문제 (그리고 매우 흔함)-내가 본 것 중 최악의 "OO"코드 중 일부는 "Data"개체와 "Code"개체를 분리 한 아키텍처로 인해 발생했습니다. 이것은 악몽 수준의 물건입니다! 문제는 비즈니스 코드의 모든 곳에서 데이터 객체에 액세스하려고 할 때 인라인으로 바로 코딩하는 경향이 있다는 것입니다 (유틸리티 클래스 또는 다른 함수를 처리하여 처리 할 수는 없지만 이것이 무엇입니까? 시간이 지남에 따라 반복적으로 발생하는 것을 보았습니다).

액세스 / 업데이트 코드는 일반적으로 수집되지 않으므로 어디에서나 중복 기능을 사용할 수 있습니다.

반면에 이러한 데이터 개체는 데이터베이스 지속성 등 유용합니다. 세 가지 해결책을 시도했습니다.

"실제"객체로 값을 복사하고 데이터 객체를 버리는 것은 지루한 일입니다 (그러나 그렇게하려는 경우 유효한 솔루션이 될 수 있습니다).

데이터 정렬 방법을 데이터 개체에 추가하면 작동 할 수 있지만 둘 이상의 작업을 수행하는 큰 지저분한 데이터 개체를 만들 수 있습니다. 많은 지속성 메커니즘이 공개 접근자를 원하기 때문에 캡슐화가 더 어려워 질 수도 있습니다 ... 완료했을 때 그것을 좋아하지는 않았지만 유효한 솔루션입니다

나에게 가장 효과적인 솔루션은 "Data"클래스를 캡슐화하고 모든 데이터 조정 기능을 포함하는 "Wrapper"클래스의 개념입니다. 그러면 데이터 클래스를 전혀 공개하지 않습니다 (세터 및 게터조차도) 반드시 필요한 경우가 아니면) 이것은 객체를 직접 조작하려는 유혹을 제거하고 대신 래퍼에 공유 기능을 추가하도록합니다.

다른 장점은 데이터 클래스가 항상 유효한 상태임을 보장 할 수 있다는 것입니다. 다음은 간단한 의사 코드 예입니다.

// Data Class
Class User {
    String name;
    Date birthday;
}

Class UserHolder {
    final private User myUser // Cannot be null or invalid

    // Quickly wrap an object after getting it from the DB
    public UserHolder(User me)
    {
        if(me == null ||me.name == null || me.age < 0)
            throw Exception
        myUser=me
    }

    // Create a new instance in code
    public UserHolder(String name, Date birthday) {
        User me=new User()
        me.name=name
        me.birthday=birthday        
        this(me)
    }
    // Methods access attributes, they try not to return them directly.
    public boolean canDrink(State state) {
        return myUser.birthday.year < Date.yearsAgo(state.drinkingAge) 
    }
}

다른 지역의 코드 전체에 연령 확인이 적용되지 않으며 생일이 무엇인지 알 수 없기 때문에 사용하려는 유혹이 없습니다. 어떤 경우에 추가 할 수 있습니다).

이 캡슐화와 안전 보장을 잃기 때문에 데이터 객체를 확장하지 않는 경향이 있습니다.이 시점에서 메소드를 데이터 클래스에 추가 할 수도 있습니다.

이렇게하면 비즈니스 로직에 데이터 액세스 정크 / 반복자가 많이 퍼져 있지 않아 읽기 쉽고 중복성이 줄어 듭니다. 또한 같은 이유로 컬렉션을 항상 래핑하는 습관을들이는 것이 좋습니다. 비즈니스 로직에서 루핑 / 검색 구문을 유지하고 항상 좋은 상태를 유지하도록하십시오.


1

생각하거나 누군가가 그렇지 않다고 알려주므로 코드를 변경하지 마십시오. 문제가 발생하면 코드를 변경하고 다른 문제를 만들지 않고 이러한 문제를 피할 수있는 방법을 찾으십시오.

당신이 좋아하지 않는 것 외에도, 당신은 변화를 만들기 위해 많은 시간을 투자하고 싶습니다. 지금 당장 문제를 적어 두십시오. 새로운 디자인이 어떻게 문제를 해결하는지 적어보십시오. 개선의 가치와 변경 비용을 파악하십시오. 그런 다음 가장 중요합니다. 이러한 변경을 완료 할 시간이 있는지 확인하십시오. 그렇지 않으면이 상태에서 절반, 해당 상태에서 절반으로 끝나게되며 이는 최악의 상황입니다. (저는 한 번에 13 가지의 다른 유형의 줄과 한 가지 유형의 표준화를 위해 세 가지 반반의 노력으로 프로젝트를 진행했습니다)


0

"OOP"범주는 설명하는 것보다 훨씬 더 크고 추상적입니다. 이 모든 것에 신경 쓰지 않습니다. 명확한 책임, 응집력, 커플 링에 관심이 있습니다. 따라서 당신이 요구하는 수준에서, "OOPS 연습"에 대해 물어 보는 것은 의미가 없습니다.

예를 들어,

MVC의 의미에 대한 오해가있는 것 같습니다. 비즈니스 로직 및 "백엔드"제어와 별도로 UI "MVC"를 호출합니다. 그러나 나를 위해 MVC에는 전체 웹 응용 프로그램이 포함되어 있습니다.

  • 모델-비즈니스 데이터 + 로직 포함
    • 모델의 구현 세부 사항으로서의 데이터 계층
  • 보기-UI 코드, HTML 템플릿, CSS 등
    • JavaScript와 같은 클라이언트 측 또는 "한 페이지"웹 응용 프로그램 등의 라이브러리를 포함합니다.
  • 제어-다른 모든 부품 사이의 서버 측 접착제
  • (ViewModel, Batch 등과 같은 확장 프로그램은 여기에 들어 가지 않습니다)

여기에는 매우 중요한 몇 가지 기본 가정이 있습니다.

  • Model 클래스 / 객체 는 다른 부분들 (View, Control, ...)에 대해 전혀 알지 못합니다 . 그것은 그들을 호출하지 않으며, 그들에 의해 호출되는 것으로 가정하지 않으며, 세션 속성 / 매개 변수 또는이 줄을 따라 다른 것을 얻지 못합니다. 완전히 혼자입니다. 이를 지원하는 언어 (예 : Ruby)에서 수동 명령 행을 실행하고, 모델 클래스를 인스턴스화하고, 하트 컨텐츠에 대해 작업하고, Control 또는 View 또는 다른 카테고리의 인스턴스없이 수행하는 모든 작업 을 수행 할 수 있습니다 . 가장 중요한 것은 세션, 사용자 등에 대한 지식이 없습니다.
  • 모델을 제외하고는 데이터 레이어를 건드리지 않습니다.
  • 뷰는 모델을 가볍게 만지지 만 (물건 표시 등) 아무것도 없습니다. (확장자는 "ViewModel"이며, 이는 모델이나 뷰에 맞지 않는 복잡한 방식으로 데이터를 렌더링하기 위해보다 실질적인 처리를 수행하는 특수 클래스입니다. 이는 부풀림을 제거 / 피할 수있는 좋은 후보입니다. 순수한 모델).
  • 제어는 가능한 한 경량이지만 다른 모든 플레이어를 모으고 그들 사이에서 물건을 옮기는 일을 담당합니다 (즉, 양식에서 사용자 항목을 추출하여 모델로 전달, 비즈니스 로직에서 유용한 예외로 전달) 사용자에 대한 오류 메시지 등). 웹 / HTTP / REST API 등의 경우 모든 권한 부여, 보안, 세션 관리, 사용자 관리 등이 여기에서 발생합니다.

중요 : UI는 MVC 의 일부 입니다. 다른 방법으로는 아닙니다 (다이어그램에서와 같이). 그것을 받아들이면, 뚱뚱한 모델은 실제로 아주 좋습니다. 실제로 그들이해서는 안되는 것들이 포함되어 있지 않다면.

"뚱뚱한 모델"은 모든 비즈니스 로직이 모델 카테고리 (패키지, 모듈, 선택한 언어의 이름이 무엇이든)에 있음을 의미합니다. 개별 클래스는 사용자가 제공하는 코딩 지침 (예 : 클래스 또는 메서드 당 최대 코드 라인)에 따라 적절한 방식으로 OOP 구조화되어야합니다.

또한 데이터 계층이 어떻게 구현되는지는 매우 중요한 결과를 초래합니다. 특히 모델 계층이 데이터 계층없이 작동 할 수 있는지 여부 (예 : 단위 테스트 또는 개발자 랩톱의 값 비싼 Oracle DB 또는 기타 제품 대신 저렴한 인 메모리 DB). 그러나 이것은 현재 우리가보고있는 아키텍처 수준에서의 구현 세부 사항입니다. 분명히 여기서는 여전히 분리를 원합니다. 즉, 순수한 도메인 로직이 데이터 액세스에 직접 인터리빙되어이를 강하게 결합시키는 코드를보고 싶지 않습니다. 다른 질문에 대한 주제.

귀하의 질문으로 돌아가려면 : 새로운 아키텍처와 내가 설명 한 MVC 체계 사이에 큰 겹치는 부분이있는 것 같습니다. 따라서 완전히 잘못된 방식은 아니지만 일부를 재발 명하는 것처럼 보입니다. 또는 현재 프로그래밍 환경 / 라이브러리가 제안하기 때문에 사용하십시오. 말하기 어려워요. 따라서 나는 당신이 의도 한 것이 특히 좋은지 나쁜지에 대한 정확한 대답을 줄 수 없습니다. 모든 "thing"에 정확히 하나의 클래스가 있는지 확인하면 알 수 있습니다. 모든 것이 응집력이 있고 결합력이 낮은 지 여부 그것은 당신에게 좋은 표시를 제공하며, 제 생각으로는 좋은 OOP 디자인 (또는 당신이 원한다면 같은 벤치 마크)에 충분합니다.

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