Sunday, September 16, 2007

Refactoring to Patterns-----Creation Method (Study Notes)

 

Replace Constructors with Creation Methods

 

Original Code :

public class Loan...
public Loan(double commitment, int riskRating, Date maturity) {
this(commitment, 0.00, riskRating, maturity, null);
}

public Loan(double commitment, int riskRating, Date maturity, Date expiry) {
this(commitment, 0.00, riskRating, maturity, expiry);
}

public Loan(double commitment, double outstanding,
int customerRating, Date maturity, Date expiry) {
this(null, commitment, outstanding, customerRating, maturity, expiry);
}

public Loan(CapitalStrategy capitalStrategy, double commitment,
int riskRating, Date maturity, Date expiry) {
this(capitalStrategy, commitment, 0.00, riskRating, maturity, expiry);
}

public Loan(CapitalStrategy capitalStrategy, double commitment,
double outstanding, int riskRating,
Date maturity, Date expiry) {
this.commitment = commitment;
this.outstanding = outstanding;
this.riskRating = riskRating;
this.maturity = maturity;
this.expiry = expiry;
this.capitalStrategy = capitalStrategy;

if (capitalStrategy == null) {
if (expiry == null)
this.capitalStrategy = new CapitalStrategyTermLoan();
else if (maturity == null)
this.capitalStrategy = new CapitalStrategyRevolver();
else
this.capitalStrategy = new CapitalStrategyRCTL();
}
}
After Refactoring 1

Public class Loan
{

//same code of last catch all constructor

private Loan(CapitalStrategy capitalStrategy, double commitment, double outstanding, int riskRating, Date maturity, Date expiry)

{.. .}
//for loantype will all fields
Public Static Loan CreateLoanAll(CapitalStrategy capitalStrategy, double commitment, double outstanding, int riskRating, Date maturity, Date expiry)
{
    return new Loan(....);
}
// for loantype1

Public Static Loan CreateLoanType1(...)
{
  return new Loan( commitment, 0.00, riskRating, maturity, null);
}
   ..... // create all other loan types like type1
}
After Refactoring 2

Public class Loan
{
....

//same code of last catch all constructor

private Loan(CapitalStrategy capitalStrategy, double commitment, double outstanding, int riskRating, Date maturity, Date expiry)

{.. .}
}
 

Public class LoanFactory

{


    Public Static Loan Static CreateLoanAll(...)



{


return new Loan(...);



  Public Static Loan Static CreateLoanType1(...)



{


return new Loan( commitment, 0.00, riskRating, maturity, null);


}


....


}


 


Test Code:



Loan myloan=LoanFactory.CreateLoanType1(..)


myloan.dowork


 


 


Chain Constructors :


Original Code:


 

public Loan(float notional, float outstanding, int rating, Date expiry) {
this.strategy = new TermROC();
this.notional = notional;
this.outstanding = outstanding;
this.rating = rating;
this.expiry = expiry;
}

public Loan(float notional, float outstanding, int rating, Date expiry, Date maturity) {
this.strategy = new RevolvingTermROC();
this.notional = notional;
this.outstanding = outstanding;
this.rating = rating;
this.expiry = expiry;
this.maturity = maturity;
}
// Catch-all constructor 
public Loan(CapitalStrategy strategy, float notional, float outstanding, int rating,
Date expiry, Date maturity) {
this.strategy = strategy;
this.notional = notional;
this.outstanding = outstanding;
this.rating = rating;
this.expiry = expiry;
this.maturity = maturity;
}

 


After Refactoring :


 

public Loan(float notional, float outstanding, int rating, Date expiry) {

this(new TermROC(), notional, outstanding, rating, expiry, null);
}
public Loan(float notional, float outstanding, int rating, Date expiry, Date maturity) {

this(new RevolvingTermROC(), notional, outstanding, rating, expiry, maturity);
}
// Catch-all constructor
public Loan(CapitalStrategy strategy, float notional, float outstanding, int rating, Date expiry, Date maturity) 
{ this.strategy = strategy; this.notional = notional; this.outstanding = outstanding; this.rating = rating; this.expiry = expiry; this.maturity = maturity; } 
}

Encapsulate Classes with Factory

original Code:
public abstract superclass(){..}
public class subclass1():superclass
{
...
}
public class subclass2():superclass
{
...
}
After Refactoring
public abstract superclass90
{

public static subclass1 createsubclass1(...){...; return subclass1;}
public static subclass2 createsubclass2(...){...;return subclass2;} 
}
 

Replace Conditional Logic with Strategy


Original Code:


public class Loan...
   public double capital() { // refactored smell because too manyc conditional logic 
      if (expiry == null && maturity != null)
         return commitment * duration() * riskFactor();
      if (expiry != null && maturity == null) {
         if (getUnusedPercentage() != 1.0)
            return commitment * getUnusedPercentage() * duration() * riskFactor();
         else
            return (outstandingRiskAmount() * duration() * riskFactor())
                + (unusedRiskAmount() * duration() * unusedRiskFactor());
      }
      return 0.0;
   }
private double outstandingRiskAmount() { return outstanding; } private double unusedRiskAmount() { return (commitment - outstanding); } public double duration() { if (expiry == null && maturity != null) return weightedAverageDuration(); else if (expiry != null && maturity == null) return yearsTo(expiry); return 0.0; } private double weightedAverageDuration() { double duration = 0.0; double weightedAverage = 0.0; double sumOfPayments = 0.0; Iterator loanPayments = payments.iterator(); while (loanPayments.hasNext()) { Payment payment = (Payment)loanPayments.next(); sumOfPayments += payment.amount(); weightedAverage += yearsTo(payment.date()) * payment.amount(); } if (commitment != 0.0) duration = weightedAverage / sumOfPayments; return duration; } private double yearsTo(Date endDate) { Date beginDate = (today == null ? start : today); return ((endDate.getTime() - beginDate.getTime()) / MILLIS_PER_DAY) / DAYS_PER_YEAR; } private double riskFactor() { return RiskFactor.getFactors().forRating(riskRating); } private double unusedRiskFactor() { return UnusedRiskFactors.getFactors().forRating(riskRating); }


 


 


After refactoring with strategy:


// For abstract class CapitalStragtegy

public abstract class CapitalStrategy {
private static final int MILLIS_PER_DAY = 86400000;
private static final int DAYS_PER_YEAR = 365;

public abstract double capital(Loan loan);

protected double riskFactorFor(Loan loan) {
return RiskFactor.getFactors().forRating(loan.getRiskRating());
}

public double duration(Loan loan) {
return yearsTo(loan.getExpiry(), loan);
}

protected double yearsTo(Date endDate, Loan loan) {
Date beginDate = (loan.getToday() == null ? loan.getStart() : loan.getToday());
return ((endDate.getTime() - beginDate.getTime()) / MILLIS_PER_DAY) / DAYS_PER_YEAR;
}
}

 


//  Code For CapitalStrategyTermLoan ;CapitalStrategyRevolver;CapitalStrategyAdvisedLine


public class CapitalStrategyTermLoan extends CapitalStrategy


{


public double capital(Loan loan)


{ return loan.getCommitment() * duration(loan) * riskFactorFor(loan); }


public double duration(Loan loan) { return weightedAverageDuration(loan); }


private double weightedAverageDuration(Loan loan) { double duration = 0.0; double weightedAverage = 0.0; double sumOfPayments = 0.0; Iterator loanPayments = loan.getPayments().iterator(); while (loanPayments.hasNext()) { Payment payment = (Payment)loanPayments.next(); sumOfPayments += payment.amount(); weightedAverage += yearsTo(payment.date(), loan) * payment.amount(); } if (loan.getCommitment() != 0.0) duration = weightedAverage / sumOfPayments; return duration; }


...


// Loan


public class Loan...
  


private CapitalStrategy capitalStrategy;


private Loan(double commitment, double outstanding, Date start, Date expiry, Date maturity, int riskRating)


{ capitalStrategy = new CapitalStrategy(); ... }


// Got Refactored


public double capital()


{ return capitalStrategy.capital(this); }


public double duration()


{ return capitalStrategy.duration(this);


}


 public static Loan newTermLoan(
      double commitment, Date start, Date maturity, int riskRating) {
      return new Loan(
         commitment, commitment, start, null, maturity, riskRating,
        
new CapitalStrategyTermLoan()
      );
   }
public static Loan newRevolver(
      double commitment, Date start, Date expiry, int riskRating) {
      return new Loan(
         commitment, 0, start, expiry, null, riskRating,
        
new CapitalStrategyRevolver()
      );
   }

   public static Loan newAdvisedLine(
      double commitment, Date start, Date expiry, int riskRating) {
      if (riskRating > 3) return null;
      Loan advisedLine = new Loan(
          commitment, 0, start, expiry, null, riskRating,
         
new CapitalStrategyAdvisedLine()
      );
      advisedLine.setUnusedPercentage(0.1);
      return advisedLine;
   }


Date getExpiry() { return expiry; } 


Date getMaturity() { return maturity; }


double getCommitment() { return commitment; }


double getUnusedPercentage() { return unusedPercentage; }


private double outstandingRiskAmount() { return outstanding; }


private double unusedRiskAmount()
{ return (commitment - outstanding); }


 


Replace Implicit Tree With Composite (Composite)


Old code :


public class OrdersWriter


{




private Orders orders;



public OrdersWriter(Orders orders) {
this.orders = orders;
}


public String getContents()


{
StringBuffer xml = new StringBuffer();
writeOrderTo(xml);
return xml.toString();


}



 


private void writeOrderTo(StringBuffer xml)


{
xml.append("<orders>");
for (int i = 0; i < orders.getOrderCount(); i++) {
Order order = orders.getOrder(i);
xml.append("<order");
xml.append(" id='");
xml.append(order.getOrderId());
xml.append("'>");
writeProductsTo(xml, order);
xml.append("</order>");
}
xml.append("</orders>");
}


private void writeProductsTo(StringBuffer xml, Order order) {
for (int j=0; j < order.getProductCount(); j++) {
Product product = order.getProduct(j);
xml.append("<product");
xml.append(" id='");
xml.append(product.getID());
xml.append("'");
xml.append(" color='");
xml.append(colorFor(product));
xml.append("'");
if (product.getSize() != ProductSize.NOT_APPLICABLE) {
xml.append(" size='");
xml.append(sizeFor(product));
xml.append("'");
}
xml.append(">");
writePriceTo(xml, product);
xml.append(product.getName());
xml.append("</product>");



}


 }


 


 After refactoring


--Create a tagnode class (component)


public class TagNode


 {
private String name = ""
private String value = ""
private StringBuffer attributes;


public TagNode(String name)


{
this.name = name;
attributes = new StringBuffer("");
}


public void addAttribute(String attribute, String value) {
attributes.append(" ");
attributes.append(attribute);
attributes.append("='");
attributes.append(value);
attributes.append("'");
}

public void addValue(String value) {
this.value = value;
}

// code for composite part 


 


private List children;

public String toString() {
String result;
result = "<" + name + attributes + ">"
Iterator it = children().iterator();
while (it.hasNext()) {
TagNode node = (TagNode)it.next();
result += node.toString();
}
result += value;
result += "</" + name + ">"
return result;
}

private List children() {
if (children == null)
children = new ArrayList();
return children;
}

public void add(TagNode child) {
children().add(child);
}

 

}

 


 // rewrite the getContents method with new Tagnode class


 


public class OrdersWriter...



public String getContents() {
StringBuffer xml = new StringBuffer();
writeOrderTo(xml);
return xml.toString();
}


private void writeOrderTo(StringBuffer xml) {
TagNode ordersTag = new TagNode("orders");
for (int i = 0; i < orders.getOrderCount(); i++) {
Order order = orders.getOrder(i);
TagNode orderTag = new TagNode("order");
orderTag.addAttribute("id", order.getOrderId());
writeProductsTo(orderTag, order);
ordersTag.add(orderTag);
}
xml.append(ordersTag.toString());
}

private void writeProductsTo(TagNode orderTag, Order order) {
for (int j=0; j < order.getProductCount(); j++) {
Product product = order.getProduct(j);
TagNode productTag = new TagNode("product");
productTag.addAttribute("id", product.getID());
productTag.addAttribute("color", colorFor(product));
if (product.getSize() != ProductSize.NOT_APPLICABLE)
productTag.addAttribute("size", sizeFor(product));
writePriceTo(productTag, product);
productTag.addValue(product.getName());
orderTag.add(productTag);
}
}

private void writePriceTo(TagNode productTag, Product product) {
TagNode priceNode = new TagNode("price");
priceNode.addAttribute("currency", currencyFor(product));
priceNode.addValue(priceFor(product));
productTag.add(priceNode);
}

Replace One/Many Distinctions With Composite


Original code :


 

public class ProductRepositoryTest extends TestCase...
private ProductRepository repository;

private Product fireTruck =
new Product("f1234", "Fire Truck",
Color.red, 8.95f, ProductSize.MEDIUM);

private Product barbieClassic =
new Product("b7654", "Barbie Classic",
Color.yellow, 15.95f, ProductSize.SMALL);

private Product frisbee =
new Product("f4321", "Frisbee",
Color.pink, 9.99f, ProductSize.LARGE);

private Product baseball =
new Product("b2343", "Baseball",
Color.white, 8.95f, ProductSize.NOT_APPLICABLE);

private Product toyConvertible =
new Product("p1112", "Toy Porsche Convertible",
Color.red, 230.00f, ProductSize.NOT_APPLICABLE);

protected void setUp() {
repository = new ProductRepository();
repository.add(fireTruck);
repository.add(barbieClassic);
repository.add(frisbee);
repository.add(baseball);
repository.add(toyConvertible);
}

 


 


private List products = new ArrayList();

public Iterator iterator() {
return products.iterator();
}

// The following two methods duplicate


public List selectBy(Spec spec) {
List foundProducts = new ArrayList();
Iterator products = iterator();
while (products.hasNext()) {
Product product = (Product)products.next();
if (spec.isSatisfiedBy(product))
foundProducts.add(product);
}
return foundProducts;
}

 


public List selectBy(List specs) {
List foundProducts = new ArrayList();
Iterator products = iterator();
while (products.hasNext()) {
Product product = (Product)products.next();
Iterator specifications = specs.iterator();
boolean satisfiesAllSpecs = true;
while (specifications.hasNext()) {
Spec productSpec = ((Spec)specifications.next());
satisfiesAllSpecs &= productSpec.isSatisfiedBy(product);
}
if (satisfiesAllSpecs)
foundProducts.add(product);
}
return foundProducts;
}


 Refactorting without Composite:


public class ProductRepository...


public List selectBy(Spec spec) {
Spec[] specs = { spec };
return selectBy(Arrays.asList(specs));
}


public List selectBy(List specs)...
// same implementation as before

 

--Note:

So, is it wise to use this solution instead of refactoring to Composite? Yes and no. It all depends on the needs of the code in question. For the system on which this example code was based, there is a need to support queries with OR, AND, and NOT conditions, like this one:

product.getColor() != targetColor ||
product.getPrice() < targetPrice

The List-based selectBy(…) method cannot support such queries. In addition, having just one selectBy(…) method is preferred so clients can call it in a uniform way.

 

 Refactorting with Composite:


// NEW CompositeSpec


public class CompositeSpec extends Spec


 {
private List specs=new ArrayList();


public CompositeSpec(List specs) {
this.specs = specs;
}

public List getSpecs()


 {
return specs;
}


}

public void add(Spec spec)

{ specs.add(spec); }

 

// Override isSatifiedBy declared in Spec


public boolean isSatisfiedBy(Product product) {
Iterator specifications = getSpecs().iterator();
boolean satisfiesAllSpecs = true;
while (specifications.hasNext()) {
Spec productSpec = ((Spec)specifications.next());
satisfiesAllSpecs &= productSpec.isSatisfiedBy(product);
}
return satisfiesAllSpecs;
}


....

 

 

public List selectBy(List specs) {
CompositeSpec spec = new CompositeSpec(specs);
List foundProducts = new ArrayList();
Iterator products = iterator();
while (products.hasNext()) {
Product product = (Product)products.next();
Iterator specifications =
spec.getSpecs().iterator();
boolean satisfiesAllSpecs = true;
while (specifications.hasNext())


 {
    Spec productSpec = ((Spec)specifications.next());


   if (spec.isSatisfiedBy(product)) // using old spec declared isSatisfriedBy
        foundProducts.add(product);
}
return foundProducts;


}


// In Test case


 


public class ProductRepositoryTest...
public void testFindByColorSizeAndBelowPrice() {
List specs = new ArrayList();
specs.add(new ColorSpec(Color.red));
specs.add(new SizeSpec(ProductSize.SMALL));
specs.add(new BelowPriceSpec(10.00));

 


List foundProducts = repository.selectBy( new CompositeSpec(specs));


// Just one selectby


 


 


//Summary :


Assume currently there are two methods:


...


public List selectBy(Spec spec) {
List foundProducts = new ArrayList();
Iterator products = iterator();
while (products.hasNext()) {
Product product = (Product)products.next();
if (spec.isSatisfiedBy(product))
foundProducts.add(product);
}
return foundProducts;
}


 


...


public List selectBy(List specs) {
List foundProducts = new ArrayList();
Iterator products = iterator();
while (products.hasNext())


            {
                Product product = (Product)products.next();
                Iterator specifications = specs.iterator();
                boolean satisfiesAllSpecs = true;
                while (specifications.hasNext())


               {
                 Spec productSpec = ((Spec)specifications.next());
                 satisfiesAllSpecs &= productSpec.isSatisfiedBy(product);
                }
              if (satisfiesAllSpecs)
            foundProducts.add(product);
             }
return foundProducts;
}


 


 


Because these two methods are simliar except a minor difference, so we can do the refactoring using Composite to create one uniform method which can handle all these two situations.


After refactoring :


// This method will handle both two above cases


public List selectBy(List specs) {
CompositeSpec spec = new CompositeSpec(specs);
List foundProducts = new ArrayList();
Iterator products = iterator();
while (products.hasNext()) {
Product product = (Product)products.next();
Iterator specifications =
spec.getSpecs().iterator();
boolean satisfiesAllSpecs = true;
while (specifications.hasNext())


 {
    Spec productSpec = ((Spec)specifications.next());


   if (spec.isSatisfiedBy(product)) // using old spec declared isSatisfriedBy
        foundProducts.add(product);
}
return foundProducts;


}


 


Createa a new class:


// NEW CompositeSpec


public class CompositeSpec extends Spec


 {
private List specs=new ArrayList();


public CompositeSpec(List specs) {
this.specs = specs;
}

public List getSpecs()


 {
return specs;
}


}

public void add(Spec spec)

{ specs.add(spec); }

 

// Override isSatifiedBy declared in Spec


public boolean isSatisfiedBy(Product product) {
Iterator specifications = getSpecs().iterator();
boolean satisfiesAllSpecs = true;
while (specifications.hasNext()) {
Spec productSpec = ((Spec)specifications.next());
satisfiesAllSpecs &= productSpec.isSatisfiedBy(product);
}
return satisfiesAllSpecs;
}


....


 


 


 


 


 


 


 


 


 


 


 


 


 


0 Comments:

Post a Comment

<< Home