Por que minha classe é pior do que a hierarquia de classes no livro (OOP para iniciantes)?

11

Estou lendo objetos, padrões e práticas do PHP . O autor está tentando modelar uma lição em uma faculdade. O objetivo é produzir o tipo de aula (palestra ou seminário) e os encargos da aula, dependendo se é uma aula de preço fixo ou por hora. Portanto, a saída deve ser

Lesson charge 20. Charge type: hourly rate. Lesson type: seminar.
Lesson charge 30. Charge type: fixed rate. Lesson type: lecture.

quando a entrada é a seguinte:

$lessons[] = new Lesson('hourly rate', 4, 'seminar');
$lessons[] = new Lesson('fixed rate', null, 'lecture');

Eu escrevi isto:

class Lesson {
    private $chargeType;
    private $duration;
    private $lessonType;

    public function __construct($chargeType, $duration, $lessonType) {
        $this->chargeType = $chargeType;
        $this->duration = $duration;
        $this->lessonType = $lessonType;
    }

    public function getChargeType() {
        return $this->getChargeType;
    }

    public function getLessonType() {
        return $this->getLessonType;
    }

    public function cost() {
        if($this->chargeType == 'fixed rate') {
            return "30";
        } else {
            return $this->duration * 5;
        }
    }
}

$lessons[] = new Lesson('hourly rate', 4, 'seminar');
$lessons[] = new Lesson('fixed rate', null, 'lecture');

foreach($lessons as $lesson) {
    print "Lesson charge {$lesson->cost()}.";
    print " Charge type: {$lesson->getChargeType()}.";
    print " Lesson type: {$lesson->getLessonType()}.";
    print "<br />";
}

Mas, de acordo com o livro, estou errado (também tenho certeza). Em vez disso, o autor forneceu uma grande hierarquia de classes como solução. Em um capítulo anterior, o autor declarou os seguintes 'quatro sinais' como o momento em que eu deveria considerar alterar minha estrutura de classe:

O único problema que vejo são as declarações condicionais e isso também de uma maneira vaga - então, por que refatorar isso? Que problemas você acha que podem surgir no futuro que eu não previ?

Atualização : esqueci de mencionar - esta é a estrutura de classes que o autor forneceu como solução - o padrão de estratégia :

O padrão de estratégia

Aditya MP
fonte
29
Não aprenda programação orientada a objetos em um livro PHP.
Giorgiosironi
4
@giorgiosironi Eu desisti de avaliar idiomas. Eu uso o PHP diariamente e, portanto, é o mais rápido para mim aprender novos conceitos. Sempre há pessoas que odeiam uma linguagem (Java: slidesha.re/91C4pX ) - é certo que é mais difícil encontrar os odiadores de Java do que o PHP discorda, mas ainda assim. Pode haver problemas com o OO do PHP, mas, no momento, não tenho tempo para aprender a sintaxe Java, o "caminho Java" e o OOP. Além disso, a programação de desktop é estranha para mim. Eu sou uma pessoa completa na web. Mas antes que você possa chegar ao JSP, você supostamente precisa saber Java desktop (nenhum citações, estou errado?)
Aditya MP
Use constantes para "taxa fixa" / "taxa horária" e "seminário" / "palestra" em vez de cadeias.
Tom Marthenal

Respostas:

19

É engraçado que o livro não o exponha claramente, mas a razão pela qual favorece uma hierarquia de classes sobre se as instruções dentro da mesma classe é provavelmente o princípio Aberto / Fechado Esta regra de design de software amplamente conhecida afirma que uma classe deve ser fechada para modificação, mas aberto para extensão.

No seu exemplo, adicionar um novo tipo de lição significaria alterar o código-fonte da classe Lesson, tornando-o frágil e propenso a regressão. Se você tivesse uma classe base e uma derivada para cada tipo de lição, seria necessário adicionar outra subclasse, que geralmente é considerada mais limpa.

Você pode colocar essa regra na seção "Declarações condicionais", se quiser, no entanto, considero essa "placa de sinalização" um pouco vaga. Se as instruções geralmente são apenas códigos, cheiros, sintomas. Eles podem resultar de uma ampla variedade de más decisões de design.

guillaume31
fonte
3
Seria uma ideia muito melhor usar uma extensão funcional em vez de herança.
DeadMG
6
Certamente existem outras / melhores abordagens para o problema. No entanto, este é claramente um livro sobre programação orientada a objetos e o princípio aberto / fechado me pareceu a maneira clássica de abordar esse tipo de dilema de design em OO.
precisa
Que tal a melhor maneira de abordar o problema ? Não faz sentido ensinar uma solução inferior apenas porque é OO.
DeadMG
16

Eu acho que o que o livro visa ensinar é evitar coisas como:

public function cost() {
    if($this->chargeType == 'fixed rate') {
        return "30";
    } else {
        return $this->duration * 5;
    }
}

Minha interpretação do livro é que você deve ter três classes:

  • ResumoLição
  • HourlyRateLesson
  • FixedRateLesson

Você deve reimplementar a costfunção nas duas subclasses.

Minha opinião pessoal

para casos simples como esse: não. É estranho que seu livro seja a favor de uma hierarquia de classes mais profunda para evitar declarações condicionais simples. Além do mais, com suas especificações de entrada, Lessonterá que ser uma fábrica com um despacho que é uma declaração condicional. Portanto, com a solução do livro, você terá:

  • 3 aulas em vez de 1
  • 1 nível de herança em vez de 0
  • o mesmo número de instruções condicionais

Isso é mais complexo sem nenhum benefício adicional.

Simon Bergot
fonte
8
Nesse caso , sim, é muita complexidade. Mas em um sistema maior, você estará adicionando mais lições, como SemesterLesson, MasterOneWeekLessonetc. Uma classe raiz abstrata, ou melhor ainda, uma interface, é definitivamente o caminho a seguir. Mas quando você tem apenas dois casos, fica a critério do autor.
Michael K
5
Eu concordo com você em geral. No entanto, exemplos educacionais são muitas vezes inventados e com arquitetura demais para ilustrar um ponto. Você ignora outras considerações por um momento para não confundir o problema em questão e as apresenta posteriormente.
22812 Karl Bielefeldt
+1 Bom detalhamento completo. O comentário "Isso é mais complexo, sem benefícios adicionais" ilustra o conceito de 'custo de oportunidade' na programação perfeita. Teoria! = Praticidade.
precisa saber é o seguinte
7

O exemplo no livro é bem estranho. De sua pergunta, a declaração original é:

O objetivo é produzir o Tipo de lição (palestra ou seminário) e as cobranças da lição, dependendo de se tratar de uma lição por hora ou preço fixo.

o que, no contexto de um capítulo sobre POO, significaria que o autor provavelmente deseja que você tenha a seguinte estrutura:

+ abstract Lesson
    - Lecture inherits Lesson
    - Seminar inherits Lesson

+ abstract Charge
    - HourlyCharge inherits Charge
    - FixedCharge inherits Charge

Mas então vem a entrada que você citou:

$lessons[] = new Lesson('hourly rate', 4, 'seminar');
$lessons[] = new Lesson('fixed rate', null, 'lecture');

ou seja, algo que é chamado de código digitado de forma estrita e que, honestamente, nesse contexto, quando o leitor pretende aprender OOP, é horrível.

Isso também significa que, se eu estiver certo sobre a intenção do autor do livro (ou seja, criar as classes abstratas e herdadas listadas acima), isso levará a códigos duplicados e bastante ilegíveis e feios:

const string $HourlyRate = 'hourly rate';
const string $FixedRate = 'fixed rate';

// [...]

Charge $charge;
switch ($chargeType)
{
    case $HourlyRate:
        $charge = new HourlyCharge();
        break;

    case $FixedRate:
        $charge = new FixedCharge();
        break;

    default:
        throw new ParserException('The value of chargeType is unexpected.');
}

// Imagine the similar code for lecture/seminar, and the similar code for both on output.

Quanto aos "letreiros":

  • Duplicação de código

    Seu código não possui duplicação. O código que o autor espera que você escreva faz.

  • A classe que sabia demais sobre seu contexto

    Sua classe apenas passa seqüências de caracteres da entrada para a saída e não sabe nada. O código esperado, por outro lado, pode ser criticado por saber demais.

  • The Jack of All Trades - Classes que tentam fazer muitas coisas

    Novamente, você está apenas passando as cordas, nada mais.

  • Declarações condicionais

    Há uma declaração condicional no seu código. É legível e fácil de entender.


Talvez, de fato, o autor esperasse que você escrevesse um código muito mais refatorado do que aquele com seis classes acima. Por exemplo, você pode usar o padrão de fábrica para lições e encargos, etc.

+ abstract Lesson
    - Lecture inherits Lesson
    - Seminar inherits Lesson

+ abstract Charge
    - HourlyCharge inherits Charge
    - FixedCharge inherits Charge

+ LessonFactory

+ ChargeFactory

+ Parser // Uses factories to transform the input into `Lesson`.

Nesse caso, você terminará com nove classes, que serão um exemplo perfeito de uma super arquitetura .

Em vez disso, você acabou com um código limpo e fácil de entender, que é dez vezes menor.

Arseni Mourzenko
fonte
Uau, suas suposições são precisas! Veja a imagem que enviei - o autor recomendou exatamente as mesmas coisas.
Aditya MP
Eu iria tão gostam de aceitar essa resposta, mas eu também gosto de @ ian31 resposta :( Oh, o que eu faço!
Aditya MP
5

Antes de tudo, você pode alterar "números mágicos" como 30 e 5 na função cost () para variáveis ​​mais significativas :)

E para ser sincero, acho que você não deve se preocupar com isso. Quando você precisar mudar de classe, então você mudará. Tente criar valor primeiro e depois passe para a refatoração.

E por que você está pensando que está errado? Essa aula não é "boa o suficiente" para você? Por que você está preocupado com algum livro;)

Michal Franc
fonte
1
Obrigado por responder! De fato, a refatoração virá mais tarde. No entanto, eu escrevi este código para a aprendizagem, tentando resolver o problema apresentado no livro do meu próprio jeito e ver como eu errei ...
Aditya MP
2
Mas você saberá isso mais tarde. Quando essa classe será usada em outras partes do sistema e você terá que adicionar algo novo. Não existe uma solução "bala de prata" :) Algo pode ser bom ou ruim, depende do sistema, requisitos etc. Ao aprender OOP, você deve falhar bastante: P Então, com o tempo, você perceberá alguns problemas e padrões comuns. Este exemplo é simples demais para dar alguns conselhos. Ohh eu realmente recomendo este post de Joel :) Arquitetura astronautas assumir joelonsoftware.com/items/2008/05/01.html
Michal Franc
@adityamenon Então sua resposta é "a refatoração virá mais tarde". Adicione as outras classes apenas quando elas forem necessárias para tornar o código mais simples - geralmente é quando o terceiro caso de uso semelhante aparece. Veja a resposta de Simon.
Izkata 5/04/12
3

Não há absolutamente nenhuma necessidade de implementar quaisquer classes adicionais. Você tem um pouco de MAGIC_NUMBERproblema em sua cost()função, mas é isso. Qualquer outra coisa é uma enorme engenharia excessiva. No entanto, infelizmente é comum que conselhos muito ruins sejam dados - o Circle herda Shape, por exemplo. Definitivamente, não é eficiente, de forma alguma, derivar uma classe para a herança simples de uma função. Você poderia usar uma abordagem funcional para personalizá-la.

DeadMG
fonte
1
Isto é interessante. Você pode explicar como faria isso com um exemplo?
precisa
1, eu concordo, não há razão para projetar / complicar demais uma funcionalidade tão pequena.
GrandmasterB
@ian31: Faça o que, especificamente?
DeadMG
@DeadMG "usar uma abordagem funcional", "usar uma extensão funcional em vez de herança"
guillaume31