Refactoring case-when statement [duplicate]

0

Il modello Product che può avere 2 tabelle di prezzo. Il metodo price calcola il prezzo del prodotto in base alla tabella dei prezzi del prodotto definita dal campo price_table .

class Product < ActiveRecord::Model
  NORMAL_PRICE_TABLE = 1
  PROMOTIONAL_PRICE_TABLE = 2

  validates_presence_of :price_table

  def price(quantity)
    case price_table
    when NORMAL_PRICE_TABLE
      # calculate using normal price table rules
    when PROMOTIONAL_PRICE_TABLE
      # calculate using promotional price table rules
    else
      raise 'unknown data price'
    end
  end
end

Voglio refactoring questo codice. Il modo più pulito che ottengo è questo:

class NormalPriceTable

  def initialize(raw_product_price, quantity)
    @raw_product_price = raw_product_price
    @quantity = quantity
  end

  def price
    # calculate
  end    
end

class PromotionalPriceTable

  def initialize(raw_product_price, quantity)
    @raw_product_price = raw_product_price
    @quantity = quantity
  end

  def price
    # calculate
  end    
end

class Product < ActiveRecord::Model
  NORMAL_PRICE_TABLE = 1
  PROMOTIONAL_PRICE_TABLE = 2

  def price(quantity)
    price_table_class.new(raw_price, quantity).price
  end

  def price_table_class
    case price_table
    when NORMAL_PRICE_TABLE
      NormalPriceTable
    when PROMOTIONAL_PRICE_TABLE
      NormalPriceTable.new(raw_price, quantity).price
    else
      raise 'unknown price table'
    end
  end
end

Due domande:

  • È possibile sbarazzarsi di questa clausola case su Product class?
  • Quali sono le altre alternative per il refactoring di questo codice?
posta Rodrigo 20.05.2015 - 21:22
fonte

2 risposte

2

Penso che nella maggior parte dei casi (non tutti?) è possibile sostituire una condizione del caso con un hash; almeno in OOP. Perché qual è la condizione del caso rispetto alla scelta del giusto valore in base al parametro di input? Questo è esattamente ciò che fa una mappa: in base a una chiave restituisce un valore.

Ma cosa succede se è necessaria una logica invece di un valore? Bene, in questo caso il valore della mappa è una funzione / metodo statico o un oggetto che implementa quella determinata logica. Ad esempio, se un nuovo oggetto è necessario ogni volta che viene chiamato quel metodo, il valore della mappa può essere una classe factory che crea tale oggetto. Nei linguaggi di programmazione strongmente tipizzati, tutti gli "oggetti valore mappa" devono implementare la stessa interfaccia o estendere la stessa classe.

Nel tuo caso la classe Product potrebbe avere questo aspetto:

class AbstractPriceTable
  def price
    raise 'unknown price table'
  end
end

class Product
  NORMAL_PRICE_TABLE = 1
  PROMOTIONAL_PRICE_TABLE = 2

  PRICE_TABLE_CLASSES = {
    1 => NormalPriceTable,
    2 => PromotionalPriceTable }

  def price(quantity)
    (PRICE_TABLE_CLASSES[price_table] || AbstractPriceTable).
      new(raw_price, quantity).price
  end
end

Con un tale costrutto, le condizioni del caso non sarebbero più necessarie. Il vantaggio della soluzione mappa è la migliore estensibilità e la sua più dinamica, in quanto è possibile "registrare" più funzioni durante il runtime. Se non lo desideri o hai bisogno di una struttura semplice e fissa, le condizioni del caso potrebbero ancora essere la scelta migliore; almeno meglio dei massivi costrutti if..elsif..elsif..else..end!

btw: ho preso l'idea di questo refactoring da questo talk .

    
risposta data 20.05.2015 - 22:16
fonte
1

Sostituzione condizionale con polimorfismo (vedere Refactoring , Martin Fowler, pagina 255) è davvero il refactoring che viene in mente.

L'hai già fatto in parte, di:

  • Creazione di classi, una per il prezzo normale, un'altra per la promozione.

  • Spostamento della logica di business nelle classi appena create.

La parte rimanente è:

  • Crea la gerarchia. Ha senso ricavare tabelle di prezzi normali e promozionali da una classe PriceTable astratta . Sebbene la classe PriceTable non contenga alcuna logica, fornirà almeno l'interfaccia comune per altre classi che utilizzano le tabelle dei prezzi.

  • Determina la posizione della separazione tra i prezzi normale e promozionale. Lo fai in Product class, ma è la migliore posizione? O sarebbe meglio iniettare il tipo di prezzo nella classe Product ? Che dire di una classe Product a cui non interessa il tipo effettivo della tabella dei prezzi, non appena corrisponde all'interfaccia della classe PriceTable astratta?

    Questa è una domanda aperta. Dipende completamente dalla tua logica aziendale e dalla parte restante del programma. Sembra che Product non debba interessare al tipo effettivo della tabella dei prezzi , non appena viene ricavato dall'estratto ProductTable , ma ciò significa anche che da qualche parte, sarai ancora devi fare la tua scelta del tipo concreto appropriato (in un switch o attraverso una mappa come suggerito da rudi ). Forse in una classe di fabbrica. Forse da qualche altra parte.

risposta data 20.05.2015 - 22:12
fonte

Leggi altre domande sui tag