비 OO에서 어떻게 프로그래밍됩니까? [닫은]


11

다른 패러다임 에 찬성하여 OOP의 단점에 대한 scathing 기사를 읽으면서 너무 많은 결함을 찾을 수없는 예를 들었습니다.

저자의 주장에 개방적이기를 원하지만 이론적으로 그 요점을 이해할 수 있지만, 한 가지 예는 특히 FP 언어에서 어떻게 더 잘 구현되는지 상상하기가 어렵습니다.

: http://www.smashcompany.com/technology/object-oriented-programming-is-an-expensive-disaster-which-must-end

// Consider the case where “SimpleProductManager” is a child of
// “ProductManager”:

public class SimpleProductManager implements ProductManager {
    private List products;

    public List getProducts() {
        return products;
    }

    public void increasePrice(int percentage) {
        if (products != null) {
            for (Product product : products) {
                double newPrice = product.getPrice().doubleValue() *
                (100 + percentage)/100;
                product.setPrice(newPrice);
            }
        }
    }

    public void setProducts(List products) {
        this.products = products;
    }
}

// There are 3 behaviors here:

getProducts()

increasePrice()

setProducts()

// Is there any rational reason why these 3 behaviors should be linked to
// the fact that in my data hierarchy I want “SimpleProductManager” to be
// a child of “ProductManager”? I can not think of any. I do not want the
// behavior of my code linked together with my definition of my data-type
// hierarchy, and yet in OOP I have no choice: all methods must go inside
// of a class, and the class declaration is also where I declare my
// data-type hierarchy:

public class SimpleProductManager implements ProductManager

// This is a disaster.

필자는 "이 3 가지 행동이 데이터 계층에 연결되어야하는 합리적인 이유가 있는가?"에 대한 필자의 주장에 대한 반박을 찾고 있지 않다.

내가 구체적으로 묻는 것은이 예제가 FP 언어 (이론적으로 실제 코드가 아닌)로 모델링 / 프로그래밍되는 방법입니다.


44
그러한 짧고 고안된 예제에서 프로그래밍 패러다임을 비교할 수는 없습니다. 여기에있는 사람이라면 누구나 자신이 선호하는 패러다임을 다른 것보다 더 잘 구현할 수 있도록하는 코드 요구 사항을 생각해 낼 수 있습니다. 실제적이고 크고 변화하는 프로젝트가있을 때만 다른 패러다임의 강점과 약점에 대한 통찰력을 얻을 수 있습니다.
Euphoric

20
OO 프로그래밍에 대해서는이 3 가지 메소드가 같은 클래스에서 함께 진행되어야한다는 것을 요구하지 않습니다. 마찬가지로 OO 프로그래밍에 대해서는 동작이 데이터와 동일한 클래스에 존재하도록 요구하지 않습니다. 즉, OO 프로그래밍을 사용하면 데이터를 동작과 동일한 클래스에 넣거나 별도의 엔티티 / 모델로 분할 할 수 있습니다. 어느 쪽이든, OO는 정말 개념부터 데이터, 객체에 관련하는 방법에 대해 할 말이 아무것도없는 객체가 근본적으로 모델링과 관련되는 동작을 클래스로 논리적으로 관련 방법을 그룹화하여합니다.
Ben Cottrell

20
기사의 저음에 10 개의 문장을 가지고 포기했습니다. 그 커튼 뒤에있는 사람에게주의를 기울이지 마십시오. 다른 소식으로, True Scotsmen이 주로 OOP 프로그래머라는 것을 몰랐습니다.
Robert Harvey

11
그러나 OO 언어로 절차 코드를 작성하는 사람의 또 다른 분노는 왜 OO가 자신을 위해 작동하지 않는지 궁금합니다.
TheCatWhisperer

11
의심 할 여지없이 OOP가 처음부터 끝까지 설계 실수를 겪는 재앙이라는 사실은 의심의 여지가 없습니다. 이 기사는 읽을 수 없으며, 예제는 기본적으로 잘못 설계된 클래스 계층 구조가 잘못 설계되었다는 주장을하는 것입니다.
Eric Lippert

답변:


42

FP 스타일에서 Product불변 클래스 product.setPriceProduct객체를 변경하지 않고 대신 새 객체를 반환하며 increasePrice함수는 "독립형"함수입니다. 당신과 비슷한 구문 (C # / Java와 같은)을 사용하면 동등한 기능이 다음과 같이 보일 수 있습니다.

 public List increasePrice(List products, int percentage) {
    if (products != null) {
        return products.Select(product => {
                double newPrice = product.getPrice().doubleValue() *
                    (100 + percentage)/100;
                return product.setPrice(newPrice);     
               });
    }
    else return null;
}

보시다시피, 핵심은 여기서 OO 예제의 "boilerplate"코드가 생략 된 것을 제외하고는 실제로 다르지 않습니다. 그러나 나는 이것을 OOP가 부풀어 오른 코드로 이끈다는 증거로 보지 않으며, 충분히 인공적인 코드 예제를 구성하면 아무것도 증명할 수 있다는 사실에 대한 증거로만 보지 않습니다.


7
이것을 "더 많은 FP"로 만드는 방법 : 1) 부분 함수 대신 전체 함수를 더 쉽게 작성하고 상위 도우미 함수를 사용하여 "if (x! = null)"를 추상화하기 위해 nullable 대신에 Maybe / Optional 유형을 사용하십시오. 논리. 2) 렌즈를 사용하여 제품 가격에 렌즈의 맥락에서 백분율 증가를 적용하는 관점에서 단일 제품의 가격 인상을 정의하십시오. 3)지도 / 선택 전화에 대한 명시적인 람다를 피하기 위해 부분 적용 / 구성 / 커리를 사용하십시오.
Jack

6
컬렉션에 대한 아이디어가 싫어서 디자인에 의해 단순히 비어있는 대신 null이 될 수 있다고 말해야합니다. 기본 튜플 / 컬렉션 지원 기능이있는 언어가 그렇게 작동합니다. OOP에서도 null컬렉션이 반환 유형 인 곳을 반환 하는 것을 싫어 합니다. / rant over
Berin Loritsch

그러나 이것은 Java 또는 C #과 같은 OOP 언어의 유틸리티 클래스와 같은 정적 메소드 일 수 있습니다. 이 코드는 목록을 전달하고 직접 보유하지 않기로하므로 부분적으로 짧습니다. 원래 코드는 또한 데이터 구조를 보유하고 있으며이를 이동 시키면 개념의 변경없이 원래 코드가 더 짧아집니다.
Mark

@ 마크 : 물론, OP는 이미 이것을 알고 있다고 생각합니다. 나는이 질문을 OOP가 아닌 언어로 의무화하지 않고 "기능적으로 표현하는 방법"으로 이해한다.
Doc Brown

@Mark FP와 OO는 서로를 배제하지 않습니다.
Pieter B

17

내가 구체적으로 묻는 것은이 예제가 FP 언어 (이론적으로 실제 코드가 아닌)로 모델링 / 프로그래밍되는 방법입니다.

"a"FP 언어로? 충분하다면 Emacs lisp를 선택합니다. 유형 (종류, 종류)의 개념을 가지고 있지만 내장 된 것만 있습니다. 따라서 예제는 "목록의 각 항목에 무언가를 곱하고 새 목록을 반환하는 방법"으로 줄어 듭니다.

(mapcar (lambda (x) (* x 2)) '(1 2 3))

당신은 간다. 다른 언어도 비슷하지만 일반적인 기능 "일치"시맨틱으로 명시 적 유형의 이점을 얻는다는 점이 다릅니다. Haskell을 확인하십시오.

incPrice :: (Num) -> [Num] -> [Num]  
incPrice _ [] = []  
incPrice percentage (x:xs) = x*percentage : incPrice percentage xs  

(또는 그와 같은 것은 나이가 들었습니다 ...)

저자의 주장에 개방적이며

왜? 나는 기사를 읽으려고 노력했다. 나는 한 페이지를 포기하고 나머지는 빨리 스캔했다.

이 기사의 문제는 OOP에 위배된다는 것이 아닙니다. 맹목적으로 "pro OOP"도 아닙니다. 논리, 기능 및 OOP 패러다임을 사용하여 가능한 경우 같은 언어로, 종종 3 가지 중 하나가없는 순전히 또는 어셈블러 수준에서 프로그래밍했습니다. 나는 그러한 패러다임이 모든 측면에서 다른 패러다임보다 훨씬 우수하다고 결코 말하지 않을 것입니다. 언어 X가 Y보다 더 좋다고 주장 할 수 있습니까? 물론입니다! 그러나 그것은 그 기사에 관한 것이 아닙니다.

이 기사의 문제는 그가 첫 문장에서 마지막 문장까지 풍부한 수사 도구 (오류)를 사용한다는 것입니다. 포함 된 모든 오류를 설명하기 시작하는 것은 완전히 무의미합니다. 저자는 그가 토론에 전혀 관심이 없으며 십자군에 있다는 것을 풍부하게 분명히합니다. 왜 귀찮게?

하루가 끝날 무렵, 그 모든 일은 일을 끝내기위한 도구 일뿐입니다. OOP가 더 나은 작업이있을 수 있고 FP가 더 나은 작업이 있거나 둘 다 과도하게 다른 작업이있을 수 있습니다. 중요한 것은 작업에 적합한 도구를 선택하여 완료하는 것입니다.


4
"그는 토론에 전혀 관심이없고, 십자군에있다"는 것을 분명히 밝히고있다.
Euphoric

Haskell 코드에 Num 제약이 필요하지 않습니까? 그렇지 않으면 어떻게 (*) 전화 할 수 있습니까?
jk.

@jk., 내가 Haskell을 해왔 던 시대였습니다. 그것은 그가 찾고있는 답변에 대한 OP의 제약을 만족시키기위한 것입니다. ;) 누군가 내 코드를 수정하려면 자유롭게 느끼십시오. 그러나 Num으로 전환하겠습니다.
AnoE

7

저자는 매우 좋은 지적을 한 후이를 백업하기 위해 부족한 예를 선택했습니다. 불만은 클래스의 구현이 아니라 데이터 계층이 함수 계층과 불가분의 관계에 있다는 아이디어입니다.

따라서 저자의 요점을 이해하기 위해 그가이 단일 클래스를 기능적인 스타일로 구현하는 방법 만 보는 것은 도움이되지 않습니다. 이 클래스 주위의 데이터 및 함수 의 전체 컨텍스트 를 기능적 스타일로 디자인하는 방법을 알아야합니다 .

제품 및 가격과 관련된 잠재적 인 데이터 유형을 고려하십시오. 이름, upc 코드, 카테고리, 운송 중량, 가격, 통화, 할인 코드, 할인 규칙 등을 브레인 스토밍하십시오.

이것은 객체 지향 디자인의 쉬운 부분입니다. 우리는 단지 위의 모든 "객체"에 대한 수업을 만들고 있습니다. Product몇 개를 합칠 수업을 만드세요 ?

그러나 기다리십시오. Set [category], (할인 코드-> 가격), (수량-> 할인 금액) 등과 같은 일부 유형의 수집 및 집계를 가질 수 있습니다. 그것들은 어디에 적합합니까? 우리 CategoryManager는 모든 종류의 범주를 추적하기 위해 별도 의 항목을 Category작성합니까 , 아니면 그 책임이 이미 작성한 클래스에 속합니까?

이제 두 개의 다른 카테고리에서 특정 수량의 품목이있는 경우 가격 할인을 제공하는 기능은 어떻습니까? 에서 그 이동합니까 Product클래스의 Category클래스의 DiscountRule클래스의 CategoryManager클래스를, 또는 우리가 새로운 무언가를 필요합니까? 이것이 우리가 다음과 같은 것을 끝내는 방법 DiscountRuleProductCategoryFactoryBuilder입니다.

함수 코드에서 데이터 계층 구조는 함수와 완전히 직교합니다. 의미 적으로 의미가있는 방식으로 함수를 정렬 할 수 있습니다. 예를 들어, 제품 가격을 변경하는 모든 기능을 그룹화 할 수 있습니다.이 경우 mapPrices다음 스칼라 예제 와 같이 공통 기능을 제외하는 것이 좋습니다 .

def mapPrices(f: Int => Int)(products: Traversable[Product]): Traversable[Product] =
  products map {x => x.copy(price = f(x.price))}

def increasePrice(percentage: Int)(price: Int): Int =
  price * (percentage + 100) / 100

mapPrices(increasePrice(25))(products)

나는 아마처럼 여기에 다른 가격 관련 기능을 추가 할 수 있습니다 decreasePrice, applyBulkDiscount

우리는 또한 모음집을 사용하기 때문에 ProductsOOP 버전은 그 모음집을 관리하는 방법을 포함해야하지만이 모듈은 제품 선택에 대한 것이 아니라 가격에 관한 것이 었습니다. 함수 데이터 커플 링을 통해 컬렉션 관리 상용구를 던져야했습니다.

products멤버를 별도의 클래스 에 배치 하여이 문제를 해결할 수는 있지만 매우 밀접하게 연결된 클래스로 끝납니다. OO 프로그래머는 함수-데이터 결합을 매우 자연스럽고 유익하다고 생각하지만 유연성 상실로 인해 비용이 많이 듭니다. 함수를 작성할 때마다 하나의 클래스에만 함수를 지정해야합니다. 함수를 사용하려면 언제든지 결합 된 데이터를 사용 지점으로 가져 오는 방법을 찾아야합니다. 이러한 제한은 엄청납니다.


2

F # ( "FP 언어")에서 이와 같이 보일 수 있다고 저자가 주장한 것처럼 데이터와 기능을 분리하면됩니다.

module Product =

    type Product = {
        Price : decimal
        ... // other properties not mentioned
    }

    let increasePrice ( percentage : int ) ( product : Product ) : Product =
        let newPrice = ... // calculate

        { product with Price = newPrice }

이런 방식으로 제품 목록에서 가격 인상을 수행 할 수 있습니다.

let percentage = 10
let products : Product list = ...  // load?

products
|> List.map (Product.increasePrice percentage)

참고 : FP에 익숙하지 않은 경우 모든 함수는 값을 반환합니다. C와 같은 언어에서 나오면 함수의 마지막 문장이 마치 return앞에 있는 것처럼 취급 할 수 있습니다 .

몇 가지 유형 주석이 포함되어 있지만 불필요합니다. 모듈이 데이터를 소유하지 않으므로 getter / setter는 필요하지 않습니다. 데이터 구조와 사용 가능한 작업을 소유합니다. 이것은 목록에서 모든 요소에 대해 함수를 실행하도록 List노출시키고 map결과를 새 목록으로 반환하는 것으로도 볼 수 있습니다 .

제품 모듈은 루핑에 대해 아무 것도 알 필요가 없습니다. 그 책임은 목록 모듈 (루핑 필요성을 만든 사람)에게 있습니다.


1

함수형 프로그래밍에 대한 전문가가 아니라는 사실로이 서문을 시작하겠습니다. 나는 더 OOP 사람입니다. 따라서 아래는 FP로 동일한 종류의 기능을 수행하는 방법이 확실하지만 잘못되었을 수 있습니다.

이것은 Typescript에 있습니다 (따라서 모든 타입 주석). Javascript와 같은 Typescript는 다중 도메인 언어입니다.

export class Product extends Object {
    name: string;
    price: number;
    category: string;
}

products: Product[] = [
    new Product( { name: "Tablet", "price": 20.99, category: 'Electronics' } ),
    new Product( { name: "Phone", "price": 500.00, category: 'Electronics' } ),
    new Product( { name: "Car", "price": 13500.00, category: 'Auto' } )
];

// find all electronics and double their price
let newProducts = products
    .filter( ( product: Product ) => product.category === 'Electronics' )
    .map( ( product: Product ) => {
        product.price *= 2;
        return product;
    } );

console.log( newProducts );

자세히 설명하면 (FP 전문가가 아닌) 미리 정의 된 동작이 많지 않다는 것을 이해해야합니다. 물론 전체 가격에 걸쳐 가격 인상을 적용하는 "가격 인상"방법은 없습니다. 물론 이것은 OOP가 아닙니다. 이러한 행동을 정의 할 클래스가 없습니다. 제품 목록을 저장하는 객체를 만드는 대신 제품 배열을 만듭니다. 그런 다음 표준 FP 절차를 사용하여 원하는 방식으로이 배열을 조작 할 수 있습니다. 특정 항목을 선택하기위한 필터, 내부를 조정하기위한 매핑 등 ... 제품 목록에 대한 제한없이 제품 목록을보다 세부적으로 제어 할 수 있습니다. SimpleProductManager가 제공하는 API. 이것은 일부 사람들에게는 이점으로 간주 될 수 있습니다. 당신이하지 않는 것도 사실입니다 ProductManager 클래스와 관련된 수하물에 대해 걱정할 필요가 없습니다. 마지막으로, 제품을 숨기는 개체가 없기 때문에 "SetProducts"또는 "GetProducts"에 대해 걱정할 필요가 없습니다. 대신 작업중인 제품 목록이 있습니다. 다시 말하지만, 이것은 당신이 말하고있는 상황 / 사람에 따라 장점 또는 단점이 될 수 있습니다. 또한 클래스가 없기 때문에 클래스 계층 구조 (그가 불평 한 것)가 분명히 없습니다. 이것은 당신이 말하고있는 상황 / 사람에 따라 장단점 일 수 있습니다. 또한 클래스가 없기 때문에 클래스 계층 구조 (그가 불평 한 것)가 분명히 없습니다. 이것은 당신이 말하고있는 상황 / 사람에 따라 장단점 일 수 있습니다. 또한 클래스가 없기 때문에 클래스 계층 구조 (그가 불평 한 것)가 분명히 없습니다.

나는 그의 전체 rant를 읽는 데 시간이 걸리지 않았다. 편리 할 때 FP 사례를 사용하지만 확실히 OOP 종류의 사람입니다. 그래서 나는 당신의 질문에 답한 이후에 그의 의견에 대해 간단한 의견을 제시 할 것이라고 생각했습니다. 나는 이것이 OOP의 "단점"을 강조하는 매우 고안된 예라고 생각한다. 이 특정 경우에 표시된 기능의 경우 OOP가 과도하게 사용되며 FP가 더 적합 할 수 있습니다. 쇼핑 카트와 같은 제품이라면 제품 목록을 보호하고 제품에 대한 액세스를 제한하는 것이 프로그램의 매우 중요한 목표이며 FP는 그러한 것을 시행 할 방법이 없습니다. 다시 말하지만, 나는 FP 전문가가 아니지만 전자 상거래 시스템을 위해 쇼핑 카트를 구현 한 경우 FP보다 OOP를 사용하는 것이 좋습니다.

개인적으로 나는 누군가를 진지하게 받아들이는 데 어려움을 겪고 있는데, "X는 끔찍하다. 항상 Y를 사용하라"는 강력한 주장을한다. 해결해야 할 다양한 문제가 있기 때문에 프로그래밍에는 다양한 도구와 패러다임이 있습니다. FP는 그 자리를 차지하고 있으며 OOP는 그 자리를 차지하고 있으며 모든 툴의 단점과 장점을 이해하지 못하고 사용시기를 알 수 없다면 훌륭한 프로그래머가 될 수 없습니다.

** 참고 :이 예제에는 Product 클래스라는 클래스가 하나 있습니다. 이 경우 단순히 바보 같은 데이터 컨테이너이지만 FP의 원칙을 위반한다고 생각하지 않습니다. 유형 검사에 도움이됩니다.

** 참고 : 내 머리 위를 기억하지 못하고지도 기능을 사용하는 방식이 제품을 제자리에서 수정하는지 여부를 확인하지 않았습니다. 정렬. 그것은 분명히 FP가 피하려고 시도하는 일종의 부작용이며, 조금 더 많은 코드를 사용하면 확실히 일어나지 않을 수 있습니다.


2
고전적인 관점에서 이것은 실제로 OOP 예제가 아닙니다. 진정한 OOP에서 데이터는 행동과 결합 될 것입니다. 여기에서 둘을 분리했습니다. 반드시 나쁜 것은 아니지만 (실제로는 더 깨끗하다고 ​​생각합니다), 고전 OOP라고 부르는 것은 아닙니다.
Robert Harvey

0

SimpleProductManager가 무언가의 자식 (확장 또는 상속) 인 것처럼 보이지 않습니다.

기본적으로 객체가 수행해야하는 작업 (동작)을 정의하는 계약 인 ProductManager 인터페이스의 구현입니다.

그것이 자식 (또는 다른 클래스 기능을 확장하는 상속 된 클래스 또는 클래스) 인 경우 다음과 같이 작성됩니다.

class SimpleProductManager extends ProductManager {
    ...
}

따라서 기본적으로 저자는 말합니다.

동작은 setProducts, incrementPrice, getProducts입니다. 또한 객체에 다른 동작이 있는지 또는 동작이 어떻게 구현되는지는 중요하지 않습니다.

SimpleProductManager 클래스가이를 구현합니다. 기본적으로 작업을 실행합니다.

주된 동작은 일부 백분율 값으로 가격을 높이는 것이므로 PercentagePriceIncreaser라고도합니다.

그러나 ValuePriceIncreaser 클래스를 구현할 수도 있습니다.

public void increasePrice(int number) {
    if (products != null) {
        for (Product product : products) {
            double newPrice = product.getPrice() + number;
            product.setPrice(newPrice);
        }
    }
}

외부 관점에서 아무것도 변경되지 않았으며 인터페이스는 동일하며 여전히 동일한 세 가지 방법이 있지만 동작은 다릅니다.

FP에 인터페이스와 같은 것이 없기 때문에 구현하기가 어렵습니다. 예를 들어 C에서는 함수에 대한 포인터를 보유하고 필요에 따라 적절한 함수를 호출 할 수 있습니다. 결국 OOP에서는 매우 유사한 방식으로 작동하지만 컴파일러에 의해 "자동화"됩니다.

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