Visitor (padrão de desenho)/Exercício 1: Cálculo de Impostos

From Wiki**3

< Visitor (padrão de desenho)

Problema

Uma agência de cobrança de impostos tornou-se famosa por permitir adaptar métodos de cobrança de impostos de forma muito flexível. Os contribuintes (Taxpayer) são pessoas (Person), empresas (Company) e regiões (Region). As pessoas trabalham nas empresas e as empresas estão sediadas em regiões. Todos têm rendimentos (as pessoas e as regiões a partir das empresas e as empresas a partir de pessoas, regiões e outras empresas) e todos devem pagar os seus impostos.

O sucesso da agência advém da facilidade com que cria variações do seu produto, o premiado FriendlyIRS. Estas aplicações permitem interrogar cada contribuinte de forma adaptada, tanto ao contribuinte, como à situação de cobrança desejada.

Assim, além da cobrança em habitual, através do seu produto VanillaTaxes, permite ainda cobrar menos impostos se o rendimento for menor que um dado valor ou se a população (número de contribuintes numa empresa ou numa região) for inferior a um dado valor de referência. Para isso, disponibiliza o seu produto BecauseWeCare.

  • A versão implementada por VanillaTaxes funciona por cobrança acumulada. Cada pessoa paga uma unidade monetária e cada empresa paga o equivalente à soma das contribuições individuais dos seus empregados. Cada região paga o equivalente à soma das contribuições das empresas nela sediadas.
  • A versão implementada por BecauseWeCare é como a anterior, mas considera ainda se o rendimento apurado é inferior a limiares de referência (BecauseWeCare.LOW, para o rendimento mínimo, mas apenas para pessoas e empresas; e BecauseWeCare.POP, para a população mínima, apenas para regiões), aplicando desconto de 10% se se verificar algum limiar.

Implemente todas as classes mencionadas:

  • Implemente esquematicamente as classes Taxpayer, Person, Company e Region, i.e., considere apenas as características estritamente necessárias para resolução do problema. As relações estruturais entre as classes devem ser explicitadas (atributos), mas não é necessário explicitar relações funcionais (métodos, interacção, etc.).
  • Implemente completamente as classes FriendlyIRS, VanillaTaxes e BecauseWeCare.

Crie um exemplo (main) que ilustre a aplicação das diferentes classes.

Solução

Taxpayer Classes

Ficheiro Taxpayer.java
/**
 * Basic taxpayer.
 */
public abstract class Taxpayer {
    /**
     * No actual value is returned in this case.
     *
     * @param irs
     *            the visitor used to compute the revenue.
     * @return tax payed by this taxpayer.
     */
    public double accept(FriendlyIRS irs) {
        throw new UnsupportedOperationException();
    }
}
Ficheiro Person.java
/**
 * Individual taxpayer.
 *
 * We omitted the initialisation code.
 */
public class Person extends Taxpayer {
    /**
     * @see Taxpayer#accept(FriendlyIRS)
     */
    @Override
    public double accept(FriendlyIRS irs) {
        return irs.taxPerson(this);
    }
}
Ficheiro Company.java
import java.util.ArrayList;

/**
 * A company has employees (persons).
 *
 * We omitted the initialisation code.
 */
public class Company extends Taxpayer {
    /**
     * The employees in this company.
     */
    private ArrayList<Person> _employees = new ArrayList<Person>();

    /**
     * Simple constructor for initialising the company with some employees.
     */
    public Company() {
        int count = (int) (Math.random() * 100);
        for (int i = 0; i < count; i++)
            _employees.add(new Person());
    }

    /**
     * @return size of company (number of employees).
     */
    public int size() {
        return _employees.size();
    }

    /**
     * @param index
     * @return an employee
     */
    public Person getEmployee(int index) {
        return _employees.get(index);
    }

    /**
     * @see Taxpayer#accept(FriendlyIRS)
     */
    @Override
    public double accept(FriendlyIRS irs) {
        return irs.taxCompany(this);
    }
}
Ficheiro Region.java
import java.util.ArrayList;

/**
 * A region has companies.
 *
 * We omitted the initialisation code.
 */
public class Region extends Taxpayer {
    /**
     * The companies in this region.
     */
    private ArrayList<Company> _companies = new ArrayList<Company>();

    /**
     * Simple constructor for initialising the region with some companies.
     */
    public Region() {
        int count = (int) (Math.random() * 100);
        for (int i = 0; i < count; i++)
            _companies.add(new Company());
    }

    /**
     * @return size of region (number of companies).
     */
    public int size() {
        return _companies.size();
    }

    /**
     * @param index
     * @return a company
     */
    public Company getCompany(int index) {
        return _companies.get(index);
    }

    /**
     * @see Taxpayer#accept(FriendlyIRS)
     */
    @Override
    public double accept(FriendlyIRS irs) {
        return irs.taxRegion(this);
    }
}

Tax Computation (Visitors)

The abstract class representing the concept of tax computation.

Ficheiro FriendlyIRS.java
/**
 * The IRS computing visitor interface.
 */
public abstract class FriendlyIRS {
    /**
     * @param person
     * @return tax payed by this person.
     */
    public abstract double taxPerson(Person person);

    /**
     * @param company
     * @return tax payed by this company.
     */
    public abstract double taxCompany(Company company);

    /**
     * @param region
     * @return tax payed by this region.
     */
    public abstract double taxRegion(Region region);
}

Simple tax computation.

Ficheiro VanillaTaxes.java
/**
 * Usual tax calculator.
 */
public class VanillaTaxes extends FriendlyIRS {

    /**
     * @see FriendlyIRS#taxCompany(Company)
     */
    @Override
    public double taxCompany(Company company) {
        double tax = 0;
        for (int index = 0; index < company.size(); index++)
            tax += company.getEmployee(index).accept(this);
        return tax;
    }

    /**
     * @see FriendlyIRS#taxPerson(Person)
     */
    @Override
    public double taxPerson(Person person) {
        return 1;
    }

    /**
     * @see FriendlyIRS#taxRegion(Region)
     */
    @Override
    public double taxRegion(Region region) {
        double tax = 0;
        for (int index = 0; index < region.size(); index++)
            tax += region.getCompany(index).accept(this);
        return tax;
    }

}

Discounted taxes.

Ficheiro BecauseWeCare.java
/**
 * The discount tax system.
 */
public class BecauseWeCare extends FriendlyIRS {

    /**
     * Low water marker for revenue.
     */
    private final int LOW = 1;

    /**
     * Low water marker for population.
     */
    private final int POP = 50;

    /**
     * @see FriendlyIRS#taxCompany(Company)
     */
    @Override
    public double taxCompany(Company company) {
        double tax = 0;
        for (int index = 0; index < company.size(); index++)
            tax += company.getEmployee(index).accept(this);
        if (company.size() < POP || tax < LOW)
            tax *= .9;
        return tax;
    }

    /**
     * In this case, we chose not to test the LOW level (simply because we know
     * the value is the same).
     *
     * @see FriendlyIRS#taxPerson(Person)
     */
    @Override
    public double taxPerson(Person person) {
        return 1;
    }

    /**
     * @see FriendlyIRS#taxRegion(Region)
     */
    @Override
    public double taxRegion(Region region) {
        double tax = 0;
        for (int index = 0; index < region.size(); index++)
            tax += region.getCompany(index).accept(this);
        if (region.size() < POP || tax < LOW)
            tax *= .9;
        return tax;
    }

}

Example Application

Simple demo application.

Ficheiro App.java
public class App {

    /**
     * @param args
     */
    public static void main(String[] args) {
        Taxpayer c1 = new Company();
        Taxpayer r1 = new Region();
        Taxpayer p1 = new Person();
        
        FriendlyIRS vt = new VanillaTaxes();
        FriendlyIRS ct = new BecauseWeCare();
        
        System.out.println("Company taxes (vanilla): " + c1.accept(vt));
        System.out.println("Region taxes (vanilla): " + r1.accept(vt));
        System.out.println("Person taxes (vanilla): " + p1.accept(vt));

        System.out.println("Company taxes (care): " + c1.accept(ct));
        System.out.println("Region taxes (care): " + r1.accept(ct));
        System.out.println("Person taxes (care): " + p1.accept(ct));
    }

}

Compiling and Running

Compiling

 javac App.java

Running

 java App

Sample outputs:

 Company taxes (vanilla): 21.0
 Region taxes (vanilla): 4631.0
 Person taxes (vanilla): 1.0
 Company taxes (care): 18.900000000000002
 Region taxes (care): 4505.800000000001
 Person taxes (care): 1.0

Region has some companies with less than the minimum number of employees:

 Company taxes (vanilla): 81.0
 Region taxes (vanilla): 192.0
 Person taxes (vanilla): 1.0
 Company taxes (care): 81.0
 Region taxes (care): 172.8
 Person taxes (care): 1.0