O principal problema com suas duas primeiras soluções é acoplar a fase de compilação e a criação / instanciação do resultado. Sua terceira maneira adiciona uma dependência de sua classe com uma maneira única de criar o resultado (CompileResult). Vamos resolver o primeiro ponto (delegando a criação de resultados):
class SuccessDetails {
private String description;
public SuccessDetails (String description) {
this.description = description;
}
}
class FailureDetails {
private String description;
public FailureDetails (String description) {
this.description = description;
}
}
class FailResultDetailed extends Result {
private FailureDetails details;
public FailResultDetailed (FailureDetails details) { this.details = details; }
}
class SuccessResultDetailed extends Result {
private SuccessDetails details;
public SuccessResultDetailed (SuccessDetails details) { this.details = details; }
}
class Result {}
class SuccessResult extends Result {
private String description;
public SuccessResult (String description) { this.description = description; }
}
class FailResult extends Result {
private String description;
public FailResult (String description) { this.description = description; }
}
interface IResultFactory {
public SuccessResultDetailed success (SuccessDetails details);
public FailResultDetailed fail (FailureDetails details);
}
class ResultFactory implements IResultFactory {
public ResultFactory () { }
public SuccessResultDetailed success (SuccessDetails details) { return new SuccessResultDetailed(details); }
public FailResultDetailed fail (FailureDetails details) { return new FailResultDetailed(details); }
}
class Compiler {
private IResultFactory resultFactory;
public Compiler (IResultFactory resultFactory) { this.resultFactory = resultFactory; }
public Result compile (String code, Boolean result) {
if (result)
return resultFactory.success(new SuccessDetails("Ok"));
else
return resultFactory.fail(new FailureDetails("You've made a mistake"));
}
}
Agora, você está livre para mudar a maneira como manipula e cria o objeto Result, mas talvez queira adicionar alguns detalhes. A maneira mais intuitiva de resolvê-lo (para um desenvolvedor OO) é extrair os detalhes em um monte de outras classes:
class SuccessWithWarningsDetails extends SuccessDetails {
private int[] lines;
public SuccessWithWarningsDetails (String description, int[] lines) {
super(description);
this.lines = lines;
}
}
class CompilerThatThrowsWarnings {
private IResultFactory resultFactory;
public CompilerThatThrowsWarnings (IResultFactory resultFactory) { this.resultFactory = resultFactory; }
public Result compile (String code, Boolean result) {
if (result)
return resultFactory.success(new SuccessWithWarningsDetails("Ok", new int[] {1, 2, 3}));
else
return resultFactory.fail(new FailureDetails("You've made a mistake"));
}
}
Mas, novamente, juntamos a fase de compilação e a criação / instanciação do resultado. Podemos tentar aplicar o mesmo processo que fizemos pela primeira vez:
interface IResultDetailsFactory {
public SuccessDetails success (String description);
public SuccessWithWarningsDetails successWithWarnings (String description, int[] lines);
public FailureDetails failure (String description);
}
class ResultDetailsFactory implements IResultDetailsFactory{
public ResultDetailsFactory () { }
public SuccessDetails success (String description) { return new SuccessDetails(description); }
public SuccessWithWarningsDetails successWithWarnings (String description, int[] lines) { return new SuccessWithWarningsDetails(description, lines); }
public FailureDetails failure (String description) { return new FailureDetails(description); }
}
class CompilerThatThrowsWarningsWithoutCreation {
private IResultFactory resultFactory;
private IResultDetailsFactory resultDetailsFactory;
public CompilerThatThrowsWarningsWithoutCreation (IResultFactory resultFactory, IResultDetailsFactory resultDetailsFactory) {
this.resultFactory = resultFactory;
this.resultDetailsFactory = resultDetailsFactory;
}
public Result compile (String code, Boolean result) {
if (result)
return resultFactory.success(resultDetailsFactory.successWithWarnings("Ok", new int[] {1, 2, 3}));
else
return resultFactory.fail(resultDetailsFactory.failure("You've made a mistake"));
}
}
Estamos tão perto do final, se apenas não tivéssemos a depuração resultFactory ... Por quê? porque nos preocupamos apenas em produzir Resultado, não em como eles são organizados.
interface IResultDetailsAndResultFactory {
public SuccessResultDetailed success (String description);
public SuccessResultDetailed successWithWarnings (String description, int[] lines);
public FailResultDetailed failure (String description);
}
class ResultDetailsAndResultFactory implements IResultDetailsAndResultFactory {
private IResultFactory resultFactory;
public ResultDetailsAndResultFactory (IResultFactory resultFactory) { this.resultFactory = resultFactory; }
public SuccessResultDetailed success (String description) { return resultFactory.success(new SuccessDetails(description)); }
public SuccessResultDetailed successWithWarnings (String description, int[] lines) { return resultFactory.success(new SuccessWithWarningsDetails(description, lines)); }
public FailResultDetailed failure (String description) { return resultFactory.fail(new FailureDetails(description)); }
}
class CompilerThatThrowsWarningsWithoutCreationAndResult {
private IResultDetailsAndResultFactory resultFactory;
public CompilerThatThrowsWarningsWithoutCreationAndResult (IResultDetailsAndResultFactory resultFactory) {
this.resultFactory = resultFactory;
}
public Result compile (String code, Boolean result) {
if (result)
return resultFactory.successWithWarnings("Ok", new int[] {1, 2, 3});
else
return resultFactory.failure("You've made a mistake");
}
}
Agora, temos não apenas uma fase de compilação que depende apenas de uma fábrica para criar resultados detalhados, mas também temos resultados que dependem apenas de uma fábrica para lidar com a maneira como eles serão processados.