완벽한 OOP 애플리케이션을 만드는 방법 [닫기]


98

최근에 'x'라는 회사에 도전하고있었습니다. 그들은 나에게 몇 가지 질문을 보내고 하나만 해결하라고 말했습니다.

문제는 다음과 같습니다.

기본 판매 세는 면제되는 도서, 식품 및 의료 제품을 제외한 모든 상품에 대해 10 %의 세율이 적용됩니다.
수입 관세는 면제없이 5 %의 세율로 모든 수입 상품에 적용되는 추가 판매 세입니다.

상품을 구매할 때 모든 상품의 이름과 가격 (세금 포함)이 적힌 영수증을 받게되며, 상품의 총 비용과 지불 한 판매 세 총액으로 마무리됩니다.
판매 세에 대한 반올림 규칙은 세율이 n % 인 경우 선반 가격 p에 판매 세가 포함됩니다 (np / 100은 가장 가까운 0.05로 반올림 됨).

"그들은 솔루션 의 디자인 측면 에 관심이 있고 내 객체 지향 프로그래밍 기술 을 평가하고 싶다고 말했습니다 ."

이것은 그들이 자신의 말로 한 것입니다.

  • 솔루션의 경우 Java, Ruby 또는 C #을 사용하는 것이 좋습니다.
  • 우리는 귀하의 솔루션의 디자인 측면에 관심이 있으며 귀하의 객체 지향 프로그래밍 기술 을 평가하고자합니다 .
  • 빌드 또는 테스트 목적으로 외부 라이브러리 또는 도구를 사용할 수 있습니다. 특히, 선택한 언어 (예 : JUnit, Ant, NUnit, NAnt, Test :: Unit, Rake 등)에 사용할 수있는 단위 테스트 라이브러리 또는 빌드 도구를 사용할 수 있습니다.
  • 선택적으로 코드와 함께 설계 및 가정에 대한 간략한 설명을 포함 할 수도 있습니다.
  • 웹 기반 응용 프로그램이나 포괄적 인 UI를 기대하지 않습니다. 오히려 우리는 단순한 콘솔 기반 애플리케이션을 기대하고 있으며 소스 코드에 관심이 있습니다.

그래서 아래에 코드를 제공했습니다. 붙여 넣기 코드를 복사하고 VS에서 실행할 수 있습니다.

class Program
 {
     static void Main(string[] args)
     {
         try
         {
             double totalBill = 0, salesTax = 0;
             List<Product> productList = getProductList();
             foreach (Product prod in productList)
             {
                 double tax = prod.ComputeSalesTax();
                 salesTax += tax;
                 totalBill += tax + (prod.Quantity * prod.ProductPrice);
                 Console.WriteLine(string.Format("Item = {0} : Quantity = {1} : Price = {2} : Tax = {3}", prod.ProductName, prod.Quantity, prod.ProductPrice + tax, tax));
             }
             Console.WriteLine("Total Tax : " + salesTax);
             Console.WriteLine("Total Bill : " + totalBill);                
        }
         catch (Exception ex)
         {
             Console.WriteLine(ex.Message);
         }
         Console.ReadLine();
     }

    private static List<Product> getProductList()
     {
         List<Product> lstProducts = new List<Product>();
         //input 1
         lstProducts.Add(new Product("Book", 12.49, 1, ProductType.ExemptedProduct, false));
         lstProducts.Add(new Product("Music CD", 14.99, 1, ProductType.TaxPaidProduct, false));
         lstProducts.Add(new Product("Chocolate Bar", .85, 1, ProductType.ExemptedProduct, false));

        //input 2
         //lstProducts.Add(new Product("Imported Chocolate", 10, 1, ProductType.ExemptedProduct,true));
         //lstProducts.Add(new Product("Imported Perfume", 47.50, 1, ProductType.TaxPaidProduct,true));

        //input 3
         //lstProducts.Add(new Product("Imported Perfume", 27.99, 1, ProductType.TaxPaidProduct,true));
         //lstProducts.Add(new Product("Perfume", 18.99, 1, ProductType.TaxPaidProduct,false));
         //lstProducts.Add(new Product("Headache Pills", 9.75, 1, ProductType.ExemptedProduct,false));
         //lstProducts.Add(new Product("Imported Chocolate", 11.25, 1, ProductType.ExemptedProduct,true));
         return lstProducts;
     }
 }

public enum ProductType
 {
     ExemptedProduct=1,
     TaxPaidProduct=2,
     //ImportedProduct=3
 }

class Product
 {
     private ProductType _typeOfProduct = ProductType.TaxPaidProduct;
     private string _productName = string.Empty;
     private double _productPrice;
     private int _quantity;
     private bool _isImportedProduct = false;

    public string ProductName { get { return _productName; } }
     public double ProductPrice { get { return _productPrice; } }
     public int Quantity { get { return _quantity; } }

    public Product(string productName, double productPrice,int quantity, ProductType type, bool isImportedProduct)
     {
         _productName = productName;
         _productPrice = productPrice;
         _quantity = quantity;
         _typeOfProduct = type;
         _isImportedProduct = isImportedProduct;
     }

    public double ComputeSalesTax()
     {
         double tax = 0;
         if(_isImportedProduct) //charge 5% tax directly
             tax+=_productPrice*.05;
         switch (_typeOfProduct)
         {
             case ProductType.ExemptedProduct: break;
             case ProductType.TaxPaidProduct:
                 tax += _productPrice * .10;
                 break;
         }
         return Math.Round(tax, 2);
         //round result before returning
     }
 }

입력을 해제하고 다른 입력에 대해 실행할 수 있습니다.

해결책을 제공했지만 거절당했습니다.

"그들은 코드 솔루션이 만족스럽지 않기 때문에 우리의 현재 공석으로 나를 고려할 수 없다고 말했습니다."

여기에 빠진 것이 무엇인지 알려주세요. 이 솔루션은 좋은 OOAD 솔루션이 아닙니다.
OOAD 기술을 어떻게 향상시킬 수 있습니까?
선배들은 완벽한 OOAD 응용 프로그램도 실제로 작동하지 않을 것이라고 말합니다.

감사


2
열거 형이 아닌 상속 계층 구조를 사용하여 제품 유형을 구별하기를 원했을까요? (나는 생각하지만 그 방법은 오히려 주어진 시나리오에 뒤얽힌 될 것이다.)
더글라스

내 생각 엔 인터페이스를 정의하지 않았기 때문에 솔루션을 거의 거부했습니다.
Chris Gessler 2012

28
경험상 누군가가 인터뷰 상황에서 OOP 기술을 보여달라고 요청하면 switch 문 사용을 피해야합니다. 대신 상속 계층 구조를 사용해야합니다.

4
코드 검토에 게시되어야합니다.
Derek

나는 거기에도 게시했지만 거기에서 좋은 해결책을 얻지 못했습니다. 그러나 모든 사람은 다른 사람의 도움을 받아 만든 새 솔루션을 볼 수 있습니다. codeproject.com/Questions/332077/… 여기 에서 새 코드도 찾을 수 있습니다.
sunder 2012

답변:


246

우선 좋은 하늘은 재정적 계산을 두 배로하지 않습니다 . 십진수로 재무 계산을 수행하십시오 . 그게 목적입니다. 재정적 문제가 아닌 물리 문제 를 해결하려면 double을 사용하십시오 .

프로그램의 주요 설계 결함은 정책이 잘못된 위치에 있다는 것 입니다. 세금 계산 책임자는 누구입니까? 당신은 넣어 한 제품을 세금을 계산 담당,하지만 당신은 사과 나 책이나 세탁기, 구매할 때 당신이 구입하려고하는 일을 당신이 지불에가는 얼마나 많은 세금을 알려주에 대한 책임을지지 않습니다 그것. 정부 정책 이이를 알려줄 책임이 있습니다. 당신의 디자인은 객체가 다른 누구 의 문제가 아니라 자신의 문제에 대해 책임 져야 한다는 기본 OO 디자인 원칙을 크게 위반합니다 . 세탁기의 우려는 올바른 수입 관세를 부과하는 것이 아니라 옷을 세탁하는 것입니다. 세법이 변경되면 변경하고 싶지 않습니다.세탁기 개체 , 정책 개체 를 변경하려는 경우 .

그렇다면 앞으로 이러한 종류의 문제에 어떻게 접근 할 것인가?

문제 설명에서 모든 중요한 명사를 강조하여 시작했을 것입니다.

기본 판매 세는 (A)에서 적용 비율 모두 10 %의 제품 을 제외하고, , 음식의료 제품 면제됩니다. 수입 관세면제 없이 5 % 의 세율 로 모든 수입 상품 에 적용 되는 추가 판매 세 입니다. 상품을 구매할 때 모든 상품이름가격 ( 세금 포함 ) 이 적힌 영수증 을 받고 총 비용으로 마무리합니다.품목 수 및 지불 된 판매 세 총액 . 판매 세에 대한 반올림 규칙은 세율 이 n % 인 경우 선반 가격 p에 판매 세 금액 (가장 가까운 0.05로 반올림 된 np / 100)이 포함 됩니다.

자, 그 모든 명사들 사이의 관계는 무엇입니까?

  • 기본 판매 세는 일종의 판매 세입니다.
  • 수입세는 일종의 판매 세입니다
  • 판매 세에는 소수점 비율이 있습니다.
  • 책은 일종의 아이템
  • 음식은 일종의 아이템입니다
  • 의료 제품은 일종의 품목입니다.
  • 수입품 일 수 있음
  • 항목에는 문자열 인 이름이 있습니다.
  • 항목에는 10 진수 인 선반 가격이 있습니다. (참고 : 항목에 실제로 가격이 있습니까? 두 개의 동일한 세탁기가 다른 상점에서 다른 가격으로 판매되거나 다른 시간에 동일한 상점에서 판매 될 수 있습니다. 더 나은 디자인은 가격 책정 정책이 항목과 관련이 있다고 말하는 것입니다. 그 가격.)
  • 판매 세 면제 정책은 품목에 판매 세가 적용되지 않는 조건을 설명합니다.
  • 영수증에는 품목, 가격 및 세금 목록이 있습니다.
  • 영수증에는 총계가 있습니다.
  • 영수증에는 총 세금이 있습니다.

... 등등. 모든 명사 사이의 모든 관계가 해결되면 클래스 계층 구조 설계를 시작할 수 있습니다. 추상 기본 클래스 항목이 있습니다. 책은 그것으로부터 물려받습니다. 추상 클래스 SalesTax가 있습니다. BasicSalesTax는 여기에서 상속됩니다. 등등.


12
방금 제공된 것보다 더 필요하십니까? 상속이 구현되는 방법과 다형성이 무엇인지에 대해 자세히 알아야 할 것 같습니다.
Induster 2012

27
@sunder :이 대답은 충분합니다. 이제이를 첫 번째 예로 사용하여 기술을 개발하는 것은 귀하의 책임입니다. 귀하의 예는 실제 예의 정의입니다. 이 실제 코드에는 제공하지 않은 실제 디자인이 필요했기 때문에 실제 인터뷰에 실패했습니다.
Greg D

9
@Narayan : double정답의 0.00000001 % 이내이면 충분합니다. 0.5 초 후에 벽돌이 얼마나 빨리 떨어지는 지 알고 싶다면 두 배로 계산하세요. 당신이 복식으로 재정적 무례 함을 할 때 당신은 세후 가격이 43.79999999999999 달러이고 정답에 매우 가깝더라도 어리석은 것처럼 보입니다.
Eric Lippert 2012

31
+1 언급 된 문제의 각 명사를 조사한 다음 서로 간의 관계를 열거하는 놀라운 연습을 강조했습니다. 좋은 생각입니다.
Chris Tonkinson 2012

3
@ Jordão : 십진수로 0.10을 10 배 더하면 1.00이됩니다. 그러나 1.0 / 333.0을 330 번 더한다고해서 반드시 십진수 나 두 배가되는 것은 아닙니다. 십진수에서는 분모가 10의 거듭 제곱 인 분수가 정확하게 표현됩니다. 복식에서는 2의 거듭 제곱을 가진 분수입니다. 다른 것은 대략적으로 표현됩니다.
Eric Lippert

38

회사가 NUnit, JUnit 또는 Test :: Unit과 같은 라이브러리에 대해 말하면 TDD가 실제로 가져올 가능성이 높습니다. 코드 샘플에는 테스트가 전혀 없습니다.

다음과 같은 실용적인 지식을 보여 주려고합니다.

  • 단위 테스트 (예 : NUnit)
  • 조롱 (예 : RhinoMocks)
  • 지속성 (예 : NHibernate)
  • IoC 컨테이너 (예 : NSpring)
  • 디자인 패턴
  • SOLID 원리

위에서 언급 한 모든 주제를 다루는 양질의 무료 스크린 캐스트의 인상적인 소스로 www.dimecasts.net 을 추천하고 싶습니다 .


19

이것은 매우 주관적이지만 여기에 코드에 대해 몇 가지 요점이 있습니다.

  • 내 생각에 당신은 혼합 ProductShoppingCartItem. Product제품 이름, 세금 상태 등은 있어야하지만 수량은 없어야합니다. 수량은 제품의 속성이 아니며 특정 제품을 구매하는 회사의 각 고객마다 다릅니다.

  • ShoppingCartItema Product와 수량 이 있어야합니다 . 이렇게하면 고객이 동일한 제품을 더 많거나 적게 구매할 수 있습니다. 현재 설정으로는 불가능합니다.

  • 최종 세금을 계산하는 것도 그 일부가 Product되어서는 안됩니다 ShoppingCart. 최종 세금 계산에는 장바구니에있는 모든 제품을 아는 것이 포함될 수 있으므로 이와 같은 일부 여야합니다 .


이 답변에 대한 유일한 문제는 더 나은 제품 지불 시스템 (유효한)을 구축하는 방법을 설명하지만 실제로 OOP 방법론에 대해 자세히 설명하지 않는다는 것입니다. 이것은 모든 언어로 구현 될 수 있습니다. 어떤 종류의 인터페이스, 상속, 다형성 등을 보여주지 않으면 그는 여전히 테스트에 실패 할 것입니다.
타임 아웃

마지막 요점과 관련하여 IMO 세금 계산에 가장 적합한 장소는 단일 책임 원칙으로 인해 별도의 TaxCalculator 클래스입니다.
Radek 2012

답장을 보내 주셔서 감사합니다. 모든 회사가 이러한 광범위하고 순수한 OOPS 모델에서 작업합니까?
sunder

@shyamsunder 내 대답에 대해 정말 순수한 것은 없습니다. OOD의 중요한 측면 인 인터페이스 / 상속을 사용하지 않지만 가장 중요한 원칙을 보여주고 있습니다. 제 생각에는 그것이 속한 곳에 책임을지고 있습니다. 다른 답변이 지적했듯이 디자인의 주된 문제는 다양한 행위자 간의 책임을 혼동하고 기능을 추가 할 때 문제가 발생한다는 것입니다. 대부분의 대형 소프트웨어는 이러한 원칙을 따르는 경우에만 성장할 수 있습니다.
xxbbcc

좋은 대답이지만 세금 계산이 별도의 대상이되어야한다는 데 동의합니다.

14

우선, 이것은 매우 좋은 인터뷰 질문입니다. 많은 기술 의 좋은 척도입니다 .

높은 수준과 낮은 수준 모두 에서 좋은 답변 ( 완벽한 답변 은 없음 ) 을 제공하기 위해 이해해야 할 사항이 많이 있습니다. 다음은 몇 가지입니다.

  • 도메인 모델링 -> 솔루션의 좋은 모델을 어떻게 생성합니까? 어떤 개체를 만드나요? 요구 사항을 어떻게 해결합니까? 명사를 찾는 것은 좋은 시작이지만, 엔티티 선택이 좋은지 어떻게 결정합니까? 필요한 다른 엔티티는 무엇입니까 ? 이를 해결하려면 어떤 도메인 지식 이 필요합니까?
  • 관심사 분리, 느슨한 결합, 높은 응집력 -> 디자인에서 관심사 나 변경 률이 다른 부분을 어떻게 분리하고 어떻게 연관 시키나요? 디자인을 유연하고 최신 상태로 유지하는 방법은 무엇입니까?
  • 단위 테스트, 리팩토링, TDD- > 솔루션을 마련하기위한 프로세스 는 무엇입니까 ? 테스트를 작성하고, 모의 객체를 사용하고, 리팩터링하고, 반복합니까?
  • 깨끗한 코드, 언어 관용구 -> 프로그래밍 언어의 기능을 사용하여 도움이됩니까? 이해할 수있는 코드를 작성합니까? 추상화 수준이 합리적입니까? 코드는 얼마나 유지 관리 할 수 ​​있습니까?
  • 도구 : 소스 제어를 사용합니까? 빌드 도구? 십오 일?

거기에서 디자인 원칙 (예 : SOLID 원칙), 디자인 패턴, 분석 패턴, 도메인 모델링, 기술 선택, 미래 발전 경로 (예 : 데이터베이스 또는 풍부한 UI 레이어를 추가하면 무엇을 변경해야합니까?), 트레이드 오프, 비 기능적 요구 사항 (성능, 유지 관리 성, 보안 등), 승인 테스트 등 ...

솔루션을 변경하는 방법에 대해서는 언급하지 않고 이러한 개념에 더 집중해야합니다.

그러나 예 (Java에서)처럼 이 문제 를 어떻게 (부분적으로) 해결 했는지 보여줄 수 있습니다 . 이 영수증을 인쇄하기 위해 모든 것이 어떻게 결합되는지 확인 하려면 Program수업 시간 을보십시오.

------------------ 이것은 귀하의 주문입니다 ------------------
(001) 도메인 기반 디자인 ----- $ 69.99
(001) 성장하는 객체 지향 소프트웨어 ----- $ 49.99
(001) House MD 시즌 1 ----- $ 29.99
(001) 하우스 MD 시즌 7 ----- $ 34.50
(IMD) 성장하는 객체 지향 소프트웨어 ----- $ 2.50
(BST) 하우스 MD 시즌 1 ----- $ 3.00
(BST) 하우스 MD 시즌 7 ----- $ 3.45
(IMD) 하우스 MD 시즌 7 ----- $ 1.73
                                소계 ----- $ 184.47
                                세금 합계 ----- $ 10.68
                                    합계 ----- $ 195.15
---------------- 저희를 선택해 주셔서 감사합니다 ----------------

당신은 확실히 그 책들을 봐야한다 :-)

주의 사항 : 내 솔루션은 여전히 ​​매우 불완전하며, 좋은 기반을 구축하기 위해 행복한 경로 시나리오에 집중했습니다.


귀하의 솔루션을 살펴본 결과 꽤 흥미로 웠습니다. 주문 클래스가 영수증 인쇄에 대한 책임이 없어야한다고 생각하지만. 마찬가지로 TaxMethod 클래스는 세금 계산을 담당하지 않아야합니다. 또한 TaxMethodPractice에는 TaxMethod 목록이 없어야합니다. 대신 SalesPolicy라는 클래스에이 목록이 포함되어야합니다. SalesEngine이라는 클래스에는 SalesPolicy, Order 및 TaxCalculator가 전달되어야합니다. SalesEngine은 주문의 항목에 SalesPolicy를 적용하고 TaxCalculator를 사용하여 세금을 계산합니다.
CKing

@bot : 흥미로운 관찰 .... 지금 Order영수증을 인쇄하지만 Receipt자체 서식에 대해 알고 있습니다. 또한 TaxMethodPractice는 일종의 세금 정책이며 특정 시나리오에 적용되는 모든 세금을 보유합니다. TaxMethods 세금 계산기입니다. 제안 된 SalesEngine과 같은 더 높은 수준의 바인딩 클래스 만 누락 된 것 같습니다. 흥미로운 아이디어입니다.
Jordão

저는 모든 클래스가 하나의 잘 정의 된 책임을 가져야하고 실제 객체를 나타내는 클래스가 실제 세계와 일치하는 방식으로 행동해야한다고 생각합니다. 이를 위해 TaxMethod를 두 개의 클래스로 나눌 수 있습니다. TaxCriteria 및 TaxCalculator. 마찬가지로 주문은 영수증을 인쇄해서는 안됩니다. 영수증을 생성하려면 ReceiptGenerator에 Receipt가 전달되어야합니다.
CKing

@bot : 전적으로 동의합니다! 좋은 디자인은 견고합니다 ! TaxMethod 세금 계산기이고 TaxEligibilityCheck 세금 기준입니다. 그들은 별개의 개체입니다. 영수증은 예, 생성 부분을 분할하면 디자인이 더욱 향상됩니다.
Jordão

1
그 아이디어는 사양 패턴 에서 비롯됩니다 .
Jordão

12

product라는 클래스를 사용하고 있다는 사실을 제외하고는 상속이 무엇인지를 보여주지 않았고 Product에서 상속 된 다중 클래스를 만들지 않았고 다형성도 없습니다. 여러 OOP 개념을 사용하여 문제를 해결할 수 있습니다 (단지 알고 있음을 보여주기 위해). 이것은 인터뷰 문제이므로 얼마나 알고 있는지 보여주고 싶습니다.

그러나 지금 당장은 우울증으로 변하지 않을 것입니다. 여기에서 그것들을 보여주지 않았다고해서 이미 그것들을 모르거나 배울 수 없다는 것을 의미하지는 않습니다.

OOP 또는 인터뷰에 대한 약간의 경험이 필요합니다.

행운을 빕니다!


실제로 이것은 제 첫 디자인이었고, 다른 하나를 만들었지 만 글자 수 제한이 초과되어 보여 드릴 수 없습니다.
sunder

어떤 예의 도움으로 그것을 보여줄 수 있습니까?
sunder 2012

@sunder : 새 디자인으로 질문을 업데이트 할 수 있습니다.
Bjarke Freund-Hansen

10

OOP로 프로그래밍을 배우기 시작한 사람들은 그것이 의미하는 바를 이해하는 데 큰 문제가 없습니다. 왜냐하면 그것이 실제 생활 과 똑같기 때문 입니다. OO가 아닌 다른 프로그래밍 가족과의 기술이 있다면 이해하기가 더 어려울 수 있습니다.

먼저 화면을 끄거나 좋아하는 IDE를 종료하십시오. 종이연필을 가지고 최종 프로그램에서 접할 수있는 모든 항목 , 관계 , 사람 , 기계 , 프로세스 , 물건 등 의 목록 을 작성하십시오.

둘째, 다른 기본 엔티티 를 얻으십시오 . 어떤 것들은 속성 이나 능력을 공유 할 수 있다는 것을 이해할 것입니다 . 당신은 그것을 추상적 인 객체 에 넣어야 합니다 . 프로그램의 멋진 스키마를 그리기 시작해야합니다.

다음으로 fonctionnalities (메소드, 함수, 서브 루틴, 원하는대로 호출)를 입력해야합니다. 예를 들어, 제품 객체는 판매 세계산할 수 없어야합니다 . 판매 엔진 개체를해야한다.

처음에 모든 큰 단어 ( 인터페이스 , 속성 , 다형성 , 유산 등)와 디자인 패턴에 문제를 느끼지 마시고 , 아름다운 코드를 만들려고조차하지 마세요 ... 단순한 객체를 생각 하고 실제 생활에서와 같이 그것 사이의 간섭 .

그 후에 이것에 대한 진지하고 간결한 글을 읽어보십시오. WikipediaWikibooksGoF와 Design PatternsUML에 대한 내용을 시작하고 읽을 수있는 정말 좋은 방법 이라고 생각 합니다 .


3
'먼저 화면 끄기'에 +1합니다. 나는 생각의 힘이 컴퓨팅의 힘으로 너무 자주 오인된다고 생각합니다.
kontur

1
+1은 연필과 종이를 사용하는 가장 간단한 방법입니다. :)이 IDE의 앞에 앉아 때 여러 번 사람들은 혼란 얻을
Neeraj Gulia을

일부 과학자들은 우리의 뇌가 화면을 볼 때 부주의하다고 말했습니다. 제가 소프트웨어 아키텍처 디자인을 공부할 때 선생님이 종이 작업을하게하세요. 그는 강력한 UML 소프트웨어를 좋아하지 않습니다. 중요한 것은 먼저 사물을 이해하는 것입니다.
smonff

4

먼저 Product클래스를 Receipt ( ShoppingCart) 클래스와 혼합하지 마십시오 . 은 & 뿐만 아니라 ( )의 quantity일부 여야합니다 . & 의 일부가되어야합니다 .ReceipItemShoppingCartItemTaxCostTotalTaxTotalCostShoppingCart

Product수업에는 다음 과 같은 Name& Price& 일부 읽기 전용 속성이 있습니다 IsImported.

class Product
{
    static readonly IDictionary<ProductType, string[]> productType_Identifiers = 
        new Dictionary<ProductType, string[]>
        {
            {ProductType.Food, new[]{ "chocolate", "chocolates" }},
            {ProductType.Medical, new[]{ "pills" }},
            {ProductType.Book, new[]{ "book" }}
        };

    public decimal ShelfPrice { get; set; }

    public string Name { get; set; }

    public bool IsImported { get { return Name.Contains("imported "); } }

    public bool IsOf(ProductType productType)
    {
        return productType_Identifiers.ContainsKey(productType) &&
            productType_Identifiers[productType].Any(x => Name.Contains(x));
    }
}

class ShoppringCart
{
    public IList<ShoppringCartItem> CartItems { get; set; }

    public decimal TotalTax { get { return CartItems.Sum(x => x.Tax); } }

    public decimal TotalCost { get { return CartItems.Sum(x => x.Cost); } }
}

class ShoppringCartItem
{
    public Product Product { get; set; }

    public int Quantity { get; set; }

    public decimal Tax { get; set; }

    public decimal Cost { get { return Quantity * (Tax + Product.ShelfPrice); } }
}

세금 계산 부분은 Product. 제품은 세금 정책을 정의하지 않으며 세금 클래스입니다. 문제의 설명에 따라 판매 세에는 세금 Basic과 두 가지 종류가 Duty있습니다. Template Method Design Pattern그것을 달성하기 위해 사용할 수 있습니다 .

abstract class SalesTax
{
    abstract public bool IsApplicable(Product item);
    abstract public decimal Rate { get; }

    public decimal Calculate(Product item)
    {
        if (IsApplicable(item))
        {
            //sales tax are that for a tax rate of n%, a shelf price of p contains (np/100)
            var tax = (item.ShelfPrice * Rate) / 100;

            //The rounding rules: rounded up to the nearest 0.05
            tax = Math.Ceiling(tax / 0.05m) * 0.05m;

            return tax;
        }

        return 0;
    }
}

class BasicSalesTax : SalesTax
{
    private ProductType[] _taxExcemptions = new[] 
    { 
        ProductType.Food, ProductType.Medical, ProductType.Book 
    };

    public override bool IsApplicable(Product item)
    {
        return !(_taxExcemptions.Any(x => item.IsOf(x)));
    }

    public override decimal Rate { get { return 10.00M; } }
}

class ImportedDutySalesTax : SalesTax
{
    public override bool IsApplicable(Product item)
    {
        return item.IsImported;
    }

    public override decimal Rate { get { return 5.00M; } }
}

그리고 마지막으로 세금을 적용하는 클래스 :

class TaxCalculator
{
    private SalesTax[] _Taxes = new SalesTax[] { new BasicSalesTax(), new ImportedDutySalesTax() };

    public void Calculate(ShoppringCart shoppringCart)
    {
        foreach (var cartItem in shoppringCart.CartItems)
        {
            cartItem.Tax = _Taxes.Sum(x => x.Calculate(cartItem.Product));
        }

    }
}

MyFiddle 에서 시험해 볼 수 있습니다 .


2

디자인 규칙에 대한 아주 좋은 출발점은 SOLID 원칙입니다.

예를 들어 Open Closed 원칙은 새 기능을 추가하려는 경우 기존 클래스에 코드를 추가 할 필요가없고 새 클래스를 추가 할 필요가 있다고 말합니다.

샘플 애플리케이션의 경우 새 판매 세를 추가하려면 새 클래스를 추가해야합니다. 규칙의 예외 인 다른 제품도 마찬가지입니다.

반올림 규칙은 분명히 별도의 클래스로 진행됩니다. 단일 책임 원칙은 모든 클래스가 단일 책임을 갖는다 고 말합니다.

코드를 직접 작성하는 것이 단순히 좋은 솔루션을 작성하고 여기에 붙여 넣는 것보다 훨씬 더 많은 이점을 가져올 것이라고 생각합니다.

완벽하게 설계된 프로그램을 작성하는 간단한 알고리즘은 다음과 같습니다.

  1. 문제를 해결하는 코드 작성
  2. 코드가 SOLID 원칙을 준수하는지 확인
  3. 1 번보다 규칙 위반이있는 경우

2

완벽한 OOP 구현은 완전히 논란의 여지가 있습니다. 귀하의 질문에서 내가 본 바에 따르면 Product, Tax, ProductDB 등과 같은 최종 가격을 계산하기 위해 수행하는 역할에 따라 코드를 모듈화 할 수 있습니다.

  1. Product추상 클래스가 될 수 있으며 Books, Food와 같은 파생 유형이 상속 될 수 있습니다. 세금 적용 가능성은 파생 유형에 따라 결정될 수 있습니다. 제품은 파생 된 클래스에 따라 세금이 적용되는지 여부를 알려줍니다.

  2. TaxCriteria 열거 형일 수 있으며 구매 중에 지정할 수 있습니다 (수입, 판매 세 적용 가능성).

  3. Tax클래스는 TaxCriteria.

  4. 을 갖는 ShoppingCartItem의해 제안 XXBBCC 제품 및 세금 인스턴스를 캡슐화 할 수 있으며 수량과 분리 제품 세부 정보, 세금 등으로 총 가격에 좋은 방법입니다

행운을 빕니다.


1

엄격하게 OOA / D 관점에서 볼 때 내가 보는 한 가지 주요 문제는 대부분의 클래스 속성이 속성 이름에 클래스의 중복 이름이 있다는 것입니다. 예 : product Price, typeOf Product . 이 경우이 클래스를 사용하는 모든 곳에서 지나치게 장황하고 다소 혼란스러운 코드 (예 : product.productName)를 갖게됩니다. 속성에서 중복 클래스 이름 접두사 / 접미사를 제거합니다.

또한 질문에서 요청한대로 구매 및 영수증 생성과 관련된 수업을 보지 못했습니다.


1

다음은 제품, 세금 등에 대한 OO 패턴의 좋은 예입니다. OO 디자인에 필수적인 인터페이스 사용에 주목하십시오.

http://www.dreamincode.net/forums/topic/185426-design-patterns-strategy/


3
나는 제품을 인터페이스로 만드는 것보다 (추상적 인) 클래스로 만드는 것을 선호합니다. 나는 각 제품을 별도의 클래스로 만들지 않을 것입니다. 기껏해야 카테고리 당 하나의 클래스를 만들 수 있습니다.
CodesInChaos 2012

@CodeInChaos-대부분의 경우 두 가지가 모두 필요하지만 아키텍트에 취직하려는 경우 추상 클래스를 통해 인터페이스를 구현하기로 선택합니다.
Chris Gessler

1
이 예제의 인터페이스는 전혀 의미가 없습니다. 그것들은 그것들을 구현하는 각 클래스에서 코드 중복으로 이어집니다. 각 클래스는 동일한 방식으로 구현합니다.
Piotr Perak

0

방문자 패턴을 사용하여 Cost with Tax 문제를 공격했습니다.

public class Tests
    {
        [SetUp]
        public void Setup()
        {
        }

        [Test]
        public void Input1Test()
        {
            var items = new List<IItem> {
                new Book("Book", 12.49M, 1, false),
                new Other("Music CD", 14.99M, 1, false),
                new Food("Chocolate Bar", 0.85M, 1, false)};

            var visitor = new ItemCostWithTaxVisitor();

            Assert.AreEqual(12.49, items[0].Accept(visitor));
            Assert.AreEqual(16.49, items[1].Accept(visitor));
            Assert.AreEqual(0.85, items[2].Accept(visitor));
        }

        [Test]
        public void Input2Test()
        {
            var items = new List<IItem> {
                new Food("Bottle of Chocolates", 10.00M, 1, true),
                new Other("Bottle of Perfume", 47.50M, 1, true)};

            var visitor = new ItemCostWithTaxVisitor();

            Assert.AreEqual(10.50, items[0].Accept(visitor));
            Assert.AreEqual(54.65, items[1].Accept(visitor));
        }

        [Test]
        public void Input3Test()
        {
            var items = new List<IItem> {
                new Other("Bottle of Perfume", 27.99M, 1, true),
                new Other("Bottle of Perfume", 18.99M, 1, false),
                new Medicine("Packet of headache pills", 9.75M, 1, false),
                new Food("Box of Chocolate", 11.25M, 1, true)};

            var visitor = new ItemCostWithTaxVisitor();

            Assert.AreEqual(32.19, items[0].Accept(visitor));
            Assert.AreEqual(20.89, items[1].Accept(visitor));
            Assert.AreEqual(9.75, items[2].Accept(visitor));
            Assert.AreEqual(11.80, items[3].Accept(visitor));
        }
    }

    public abstract class IItem : IItemVisitable
    { 
        public IItem(string name,
            decimal price,
            int quantity,
            bool isImported)
            {
                Name = name;
                Price = price;
                Quantity = quantity;
                IsImported = isImported;
            }

        public string Name { get; set; }
        public decimal Price { get; set; }
        public int Quantity { get; set; }
        public bool IsImported { get; set; }

        public abstract decimal Accept(IItemVisitor visitor);
    }

    public class Other : IItem, IItemVisitable
    {
        public Other(string name, decimal price, int quantity, bool isImported) : base(name, price, quantity, isImported)
        {
        }

        public override decimal Accept(IItemVisitor visitor) => Math.Round(visitor.Visit(this), 2);
    }

    public class Book : IItem, IItemVisitable
    {
        public Book(string name, decimal price, int quantity, bool isImported) : base(name, price, quantity, isImported)
        {
        }

        public override decimal Accept(IItemVisitor visitor) => Math.Round(visitor.Visit(this),2);
    }

    public class Food : IItem, IItemVisitable
    {
        public Food(string name, decimal price, int quantity, bool isImported) : base(name, price, quantity, isImported)
        {
        }

        public override decimal Accept(IItemVisitor visitor) => Math.Round(visitor.Visit(this), 2);
    }

    public class Medicine : IItem, IItemVisitable
    {
        public Medicine(string name, decimal price, int quantity, bool isImported) : base(name, price, quantity, isImported)
        {
        }

        public override decimal Accept(IItemVisitor visitor) => Math.Round(visitor.Visit(this), 2);
    }

    public interface IItemVisitable
    {
        decimal Accept(IItemVisitor visitor);
    }

    public class ItemCostWithTaxVisitor : IItemVisitor
    {
        public decimal Visit(Food item) => CalculateCostWithTax(item);

        public decimal Visit(Book item) => CalculateCostWithTax(item);

        public decimal Visit(Medicine item) => CalculateCostWithTax(item);

        public decimal CalculateCostWithTax(IItem item) => item.IsImported ?
            Math.Round(item.Price * item.Quantity * .05M * 20.0M, MidpointRounding.AwayFromZero) / 20.0M + (item.Price * item.Quantity)
            : item.Price * item.Quantity;

        public decimal Visit(Other item) => item.IsImported ?
            Math.Round(item.Price * item.Quantity * .15M * 20.0M, MidpointRounding.AwayFromZero) / 20.0M + (item.Price * item.Quantity)
            : Math.Round(item.Price * item.Quantity * .10M * 20.0M, MidpointRounding.AwayFromZero) / 20.0M + (item.Price * item.Quantity);
    }

    public interface IItemVisitor
    {
        decimal Visit(Food item);
        decimal Visit(Book item);
        decimal Visit(Medicine item);
        decimal Visit(Other item);
    }

stackoverflow에 오신 것을 환영합니다. 질문에 대한 답변을 설명해주십시오. OP는 단순히 솔루션을 찾는 것이 아니라 솔루션이 더 나은 / 나쁜 이유를 찾는 것입니다.
Simon.SA
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.