Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Challenge Solution Creditas #373

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions backend/individual-assignment/src/OrderProcessor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.OrderProcessor = void 0;
class OrderProcessor {
constructor(shippingStrategy, paymentStrategy) {
this.shippingStrategy = shippingStrategy;
this.paymentStrategy = paymentStrategy;
}
processOrder(order) {
this.paymentStrategy.processPayment(order);
this.shippingStrategy.ship(order);
order.close();
}
}
exports.OrderProcessor = OrderProcessor;
16 changes: 16 additions & 0 deletions backend/individual-assignment/src/OrderProcessor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Order } from './models/Order';
import { PaymentStrategy } from './interfaces/PaymentStrategy';
import { ShippingStrategy } from './interfaces/ShippingStrategy';

export class OrderProcessor {
constructor(
private shippingStrategy: ShippingStrategy,
private paymentStrategy: PaymentStrategy
) {}

processOrder(order: Order): void {
this.paymentStrategy.processPayment(order);
this.shippingStrategy.ship(order);
order.close();
}
}
59 changes: 59 additions & 0 deletions backend/individual-assignment/src/SolutionDescription.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Alterações e Melhorias de Código:

## As alterações no código visam aplicar um Design Orientado a Objetos e Princípios de Programação Orientada a Objetos, respeitando também as normas do SOLID para uma arquitetura limpa, flexível, escalável e de fácil manutenção!

### 1. Separar as Responsábilidades

O código inicial estava concentrado em uma única estrutura com os serviços dentro de metodos, como por exemplo, o metodo *pay* da classe *Order* que tinham diversas verificações, como tipo de produto, serviço de pagamento entre outros.

Para separar as responsábilidades, foram adicionadas interfaces para:

* PaymentStrategy;
* ShippingStrategy;
* ReceiptSender;
* DiscountVoucherApplier;
* PaymentMethod

Essas interfaces são contratos que as classes que as implementarem seguirão, permitindo que cada forma de pagamento seja implementada em sua respectiva classe sem precisar modificar código para isso!
Para os serviços de pagamento e envios, foram criadas classes estrategicas que executam essas funções:

* DigitalProductPayment;
* PhysicalProductShipping;
* BookShipping;
* DigitalDiscountVoucherApplier;
* DigitalReceiptSender;
* MembershipActivationShipping;
* PhysicalProductShipping;
* NoShipping;

Essas classes, implementam as interfaces deixando o código mais dinamico e de fácil manutenção.

### 2. Composição de Herança

No código original a logica era embutida dentro das classes, então passamos a usar o princípio de composição de herança.

Colocamos a classe *OrderProcessor* para organizar os pagamentos e enviar para as estrategias específicas de cada produto.

Com isso, novas estrategias podem ser adicionadas sem alterar a classe *OrderProcessor*, facilitando a escalação do código e reduzindo o acoplamento, uma vez que as lógicas de pagamento e envio ficam sendo gerenciadas pelas estratégias.

### 3. Separação de Lógicas Específicas

Aplicando a responsábilidade única, criamos classes específicas para cada lógica (envio e pagamento de produtos físicos, digitais, livros e assinaturas), assim cada classe tem responsábilidade apenas com o seu serviço para o seu tipo de produto específico.

Caso futuramente existam alterações nas lógicas específicas de algum tipo de produto, poderá ser implementada sem impactar nas outras classes do projeto.

### 4. Metodos e Funcionalidades

No código original, os serviços estavam sendo feitos diretamente na classe de pagamento, então foram criadas interfaces adicionais:

* ReceiptSender;
* DigitalDiscountVoucherApplier;

Elas definem os acordos que serão implementados nessas operações, ambas foram implementadas na classe *DigitalProductPayment* para realizar essa tarefa de enviar a descrição da compra para o email do cliente e conceder o voucher de desconto.

Diferentes implementações de envio de recibos e aplicação de vouchers poderão ser usadas sem alterar a lógica de pagamento além de permitir que cada classe tenha apenas uma tarefa específica.

#### Dentre as implementações realizadas, considero importante ressaltar o *"Open Closed Principle"* deixando todas as classes prontas e abertas para futuras implementações e melhorias mas fechadas para modificações.

#### Estão sinalizados também os metodos onde as implementações de serviços como envio de e-mails, imprimir o shipping label, etc, devem ser feitas!

46 changes: 46 additions & 0 deletions backend/individual-assignment/src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const Order_1 = require("./models/Order");
const Customer_1 = require("./models/Customer");
const Address_1 = require("./models/Address");
const Product_1 = require("./models/Product");
const OrderProcessor_1 = require("./OrderProcessor");
const DigitalProductPayment_1 = require("./strategies/DigitalProductPayment");
const PhysicalProductShipping_1 = require("./strategies/PhysicalProductShipping");
const BookShipping_1 = require("./strategies/BookShipping");
const MembershipActivationShipping_1 = require("./strategies/MembershipActivationShipping");
const NoShipping_1 = require("./strategies/NoShipping");
const DigitalReceiptSender_1 = require("./strategies/DigitalReceiptSender");
const DigitalDiscountVoucherApplier_1 = require("./strategies/DigitalDiscountVoucherApplier");
// Exemplo de uso
const shirt = new Product_1.Product("Flowered t-shirt", Product_1.ProductType.PHYSICAL, 35.00);
const netflix = new Product_1.Product("Familiar plan", Product_1.ProductType.MEMBERSHIP, 29.90);
const book = new Product_1.Product("The Hitchhiker's Guide to the Galaxy", Product_1.ProductType.BOOK, 120.00);
const music = new Product_1.Product("Stairway to Heaven", Product_1.ProductType.DIGITAL, 5.00);
// Pedido de Produto Físico
const order1 = new Order_1.Order(new Customer_1.Customer(), new Address_1.Address());
order1.addProduct(shirt, 2);
const physicalPaymentStrategy = new DigitalProductPayment_1.DigitalProductPayment(new DigitalReceiptSender_1.DigitalReceiptSender(), new DigitalDiscountVoucherApplier_1.DigitalDiscountVoucherApplier());
const physicalShippingStrategy = new PhysicalProductShipping_1.PhysicalProductShipping();
const orderProcessor1 = new OrderProcessor_1.OrderProcessor(physicalShippingStrategy, physicalPaymentStrategy);
orderProcessor1.processOrder(order1);
// Pedido de Assinatura de Serviço
const order2 = new Order_1.Order(new Customer_1.Customer(), new Address_1.Address());
order2.addProduct(netflix, 1);
const membershipPaymentStrategy = new DigitalProductPayment_1.DigitalProductPayment(new DigitalReceiptSender_1.DigitalReceiptSender(), new DigitalDiscountVoucherApplier_1.DigitalDiscountVoucherApplier());
const membershipShippingStrategy = new MembershipActivationShipping_1.MembershipActivationShipping();
const orderProcessor2 = new OrderProcessor_1.OrderProcessor(membershipShippingStrategy, membershipPaymentStrategy);
orderProcessor2.processOrder(order2);
// Pedido de Livro
const order3 = new Order_1.Order(new Customer_1.Customer(), new Address_1.Address());
order3.addProduct(book, 1);
const bookPaymentStrategy = new DigitalProductPayment_1.DigitalProductPayment(new DigitalReceiptSender_1.DigitalReceiptSender(), new DigitalDiscountVoucherApplier_1.DigitalDiscountVoucherApplier());
const bookShippingStrategy = new BookShipping_1.BookShipping();
const orderProcessor3 = new OrderProcessor_1.OrderProcessor(bookShippingStrategy, bookPaymentStrategy);
orderProcessor3.processOrder(order3);
// Pedido de Mídia Digital
const order4 = new Order_1.Order(new Customer_1.Customer(), new Address_1.Address());
order4.addProduct(music, 1);
const digitalPaymentStrategy = new DigitalProductPayment_1.DigitalProductPayment(new DigitalReceiptSender_1.DigitalReceiptSender(), new DigitalDiscountVoucherApplier_1.DigitalDiscountVoucherApplier());
const orderProcessor4 = new OrderProcessor_1.OrderProcessor(new NoShipping_1.NoShipping(), digitalPaymentStrategy);
orderProcessor4.processOrder(order4);
69 changes: 69 additions & 0 deletions backend/individual-assignment/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { Order } from './models/Order';
import { Customer } from './models/Customer';
import { Address } from './models/Address';
import { Product, ProductType } from './models/Product';
import { OrderProcessor } from './OrderProcessor';
import { DigitalProductPayment } from './strategies/DigitalProductPayment';
import { PhysicalProductShipping } from './strategies/PhysicalProductShipping';
import { BookShipping } from './strategies/BookShipping';
import { MembershipActivationShipping } from './strategies/MembershipActivationShipping';
import { NoShipping } from './strategies/NoShipping';
import { DigitalReceiptSender } from './strategies/DigitalReceiptSender';
import { DigitalDiscountVoucherApplier } from './strategies/DigitalDiscountVoucherApplier';

// Exemplo de uso

const shirt = new Product("Flowered t-shirt", ProductType.PHYSICAL, 35.00);
const netflix = new Product("Familiar plan", ProductType.MEMBERSHIP, 29.90);
const book = new Product("The Hitchhiker's Guide to the Galaxy", ProductType.BOOK, 120.00);
const music = new Product("Stairway to Heaven", ProductType.DIGITAL, 5.00);

// Pedido de Produto Físico
const order1 = new Order(new Customer(), new Address());
order1.addProduct(shirt, 2);

const physicalPaymentStrategy = new DigitalProductPayment(
new DigitalReceiptSender(),
new DigitalDiscountVoucherApplier()
);
const physicalShippingStrategy = new PhysicalProductShipping();

const orderProcessor1 = new OrderProcessor(physicalShippingStrategy, physicalPaymentStrategy);
orderProcessor1.processOrder(order1);

// Pedido de Assinatura de Serviço
const order2 = new Order(new Customer(), new Address());
order2.addProduct(netflix, 1);

const membershipPaymentStrategy = new DigitalProductPayment(
new DigitalReceiptSender(),
new DigitalDiscountVoucherApplier()
);
const membershipShippingStrategy = new MembershipActivationShipping();

const orderProcessor2 = new OrderProcessor(membershipShippingStrategy, membershipPaymentStrategy);
orderProcessor2.processOrder(order2);

// Pedido de Livro
const order3 = new Order(new Customer(), new Address());
order3.addProduct(book, 1);

const bookPaymentStrategy = new DigitalProductPayment(
new DigitalReceiptSender(),
new DigitalDiscountVoucherApplier()
);
const bookShippingStrategy = new BookShipping();

const orderProcessor3 = new OrderProcessor(bookShippingStrategy, bookPaymentStrategy);
orderProcessor3.processOrder(order3);

// Pedido de Mídia Digital
const order4 = new Order(new Customer(), new Address());
order4.addProduct(music, 1);

const digitalPaymentStrategy = new DigitalProductPayment(
new DigitalReceiptSender(),
new DigitalDiscountVoucherApplier()
);
const orderProcessor4 = new OrderProcessor(new NoShipping(), digitalPaymentStrategy);
orderProcessor4.processOrder(order4);
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { Customer } from '../models/Customer';

export interface DiscountVoucherApplier {
applyDiscountVoucher(customer: Customer): void;
}
2 changes: 2 additions & 0 deletions backend/individual-assignment/src/interfaces/PaymentMethod.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
3 changes: 3 additions & 0 deletions backend/individual-assignment/src/interfaces/PaymentMethod.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export interface PaymentMethod {
// Interface para métodos de pagamento
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { Order } from '../models/Order';

export interface PaymentStrategy {
processPayment(order: Order): void;
}
2 changes: 2 additions & 0 deletions backend/individual-assignment/src/interfaces/ReceiptSender.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
5 changes: 5 additions & 0 deletions backend/individual-assignment/src/interfaces/ReceiptSender.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { Customer } from '../models/Customer';

export interface ReceiptSender {
sendReceipt(customer: Customer): void;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { Order } from '../models/Order';

export interface ShippingStrategy {
ship(order: Order): void;
}
6 changes: 6 additions & 0 deletions backend/individual-assignment/src/models/Address.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Address = void 0;
class Address {
}
exports.Address = Address;
1 change: 1 addition & 0 deletions backend/individual-assignment/src/models/Address.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export class Address{}
9 changes: 9 additions & 0 deletions backend/individual-assignment/src/models/CreditCard.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.CreditCard = void 0;
class CreditCard {
constructor(number) {
this.number = number;
}
}
exports.CreditCard = CreditCard;
5 changes: 5 additions & 0 deletions backend/individual-assignment/src/models/CreditCard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { PaymentMethod } from '../interfaces/PaymentMethod';

export class CreditCard implements PaymentMethod {
constructor(public number: string) {}
}
6 changes: 6 additions & 0 deletions backend/individual-assignment/src/models/Customer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Customer = void 0;
class Customer {
}
exports.Customer = Customer;
1 change: 1 addition & 0 deletions backend/individual-assignment/src/models/Customer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export class Customer {}
11 changes: 11 additions & 0 deletions backend/individual-assignment/src/models/Invoice.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Invoice = void 0;
class Invoice {
constructor(order) {
this.order = order;
this.billingAddress = order.address;
this.shippingAddress = order.address;
}
}
exports.Invoice = Invoice;
12 changes: 12 additions & 0 deletions backend/individual-assignment/src/models/Invoice.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Address } from './Address';
import { Order } from './Order';

export class Invoice {
public billingAddress: Address;
public shippingAddress: Address;

constructor(public order: Order) {
this.billingAddress = order.address;
this.shippingAddress = order.address;
}
}
27 changes: 27 additions & 0 deletions backend/individual-assignment/src/models/Order.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Order = void 0;
const OrderItem_1 = require("./OrderItem");
class Order {
constructor(customer, address) {
this.customer = customer;
this.address = address;
this.items = [];
this.closedAt = null;
this.payment = null;
}
get totalAmount() {
return this.items.reduce((sum, item) => sum + item.total, 0);
}
addProduct(product, quantity) {
const productAlreadyAdded = this.items.some(item => item.product === product);
if (productAlreadyAdded) {
throw new Error("The product has already been added. Change the amount if you want more.");
}
this.items.push(new OrderItem_1.OrderItem(product, quantity));
}
close() {
this.closedAt = new Date();
}
}
exports.Order = Order;
44 changes: 44 additions & 0 deletions backend/individual-assignment/src/models/Order.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { OrderItem } from './OrderItem';
import { Customer } from './Customer';
import { Address } from './Address';
import { Payment } from './Payment';
import { Product } from './Product';
import{PaymentStrategy} from '../interfaces/PaymentStrategy';

export class Order {
public items: OrderItem[] = [];
public closedAt: Date | null = null;
public payment: Payment | null = null;

constructor(public customer: Customer, public address: Address) {}

get totalAmount(): number {
return this.items.reduce((sum, item) => sum + item.total, 0);
}

addProduct(product: Product, quantity: number): void {
const productAlreadyAdded = this.items.some(item => item.product === product);
if (productAlreadyAdded) {
throw new Error("The product has already been added. Change the amount if you want more.");
}

this.items.push(new OrderItem(product, quantity));
}

pay(strategy: PaymentStrategy): void {
if (this.payment) {
throw new Error("The order has already been paid!");
}

if (this.items.length === 0) {
throw new Error("Empty order cannot be paid!");
}

strategy.processPayment(this);
this.close();
}

public close(): void {
this.closedAt = new Date();
}
}
Loading