Atualmente, estou trabalhando em um projeto de software que realiza compactação e indexação em imagens de vigilância por vídeo. A compactação funciona dividindo objetos em segundo plano e em primeiro plano, salvando o plano de fundo como uma imagem estática e o primeiro plano como um sprite.
Recentemente, comecei a revisar algumas das classes que criei para o projeto.
Percebi que existem muitas classes que possuem apenas um único método público. Algumas dessas classes são:
- VideoCompressor (com um
compress
método que captura um vídeo de entrada do tipoRawVideo
e retorna um vídeo de saída do tipoCompressedVideo
). - VideoSplitter (com um
split
método que capta um vídeo de entrada do tipoRawVideo
e retorna um vetor de 2 vídeos de saída, cada um do tipoRawVideo
). - VideoIndexer (com um
index
método que recebe um vídeo de entrada do tipoRawVideo
e retorna um índice de vídeo do tipoVideoIndex
).
Encontro-me instanciar cada classe apenas para fazer chamadas como VideoCompressor.compress(...)
, VideoSplitter.split(...)
, VideoIndexer.index(...)
.
Aparentemente, acho que os nomes das classes são suficientemente descritivos de sua função pretendida e são na verdade substantivos. Correspondentemente, seus métodos também são verbos.
Isso é realmente um problema?
fonte
Respostas:
Não, isso não é um problema, muito pelo contrário. É um sinal de modularidade e clara responsabilidade da classe. A interface enxuta é fácil de entender do ponto de vista de um usuário dessa classe e incentivará o acoplamento frouxo. Isso tem muitas vantagens, mas quase nenhuma desvantagem. Eu gostaria que mais componentes fossem projetados dessa maneira!
fonte
Não é mais orientado a objetos. Como essas classes não representam nada, elas são apenas vasos para as funções.
Isso não significa que está errado. Se a funcionalidade for suficientemente complexa ou genérica (ou seja, os argumentos são interfaces, não tipos finais concretos), faz sentido colocar essa funcionalidade em um módulo separado .
A partir daí, depende do seu idioma. Se o idioma tiver funções livres, eles deverão ser módulos que exportam funções. Por que fingir que é uma aula quando não é? Se a linguagem não possui funções livres como, por exemplo, Java, você cria classes com um único método público. Bem, isso apenas mostra os limites do design orientado a objetos. Às vezes, funcional é simplesmente uma combinação melhor.
Há um caso em que você pode precisar de uma classe com um método público único, porque ele precisa implementar uma interface com um método público único. Seja pelo padrão do observador ou pela injeção de dependência ou qualquer outra coisa. Aqui, novamente, depende do idioma. Em linguagens que possuem functores de primeira classe (C ++ (
std::function
ou parâmetro de modelo), C # (delegado), Python, Perl, Ruby (proc
), Lisp, Haskell, ...) esses padrões usam tipos de função e não precisam de classes. Java (ainda, na versão 8) não possui tipos de função, portanto, você usa interfaces de método único e classes de método único correspondentes.É claro que não estou defendendo escrever uma única função enorme. Ele deve ter sub-rotinas privadas, mas elas podem ser privadas para o arquivo de implementação (espaço para nome estático ou anônimo no nível do arquivo em C ++) ou em uma classe auxiliar privada que é instanciada apenas dentro da função pública ( Para armazenar dados ou não? ).
fonte
Pode haver razões para extrair um determinado método em uma classe dedicada. Uma dessas razões é permitir a injeção de dependência.
Imagine que você tem uma classe chamada
VideoExporter
que, eventualmente, poderá compactar um vídeo. Uma maneira limpa seria ter uma interface:que seria implementado assim:
e usado assim:
Uma alternativa ruim seria ter um
VideoExporter
método que possua muitos métodos públicos e faça todo o trabalho, incluindo a compactação. Isso rapidamente se tornaria um pesadelo de manutenção, dificultando a adição de suporte para outros formatos de vídeo.fonte
Este é um sinal de que você deseja passar funções como argumentos para outras funções. Suponho que sua linguagem (Java?) Não a suporta; se for esse o caso, não é tanto uma falha no seu design quanto uma falha no seu idioma de escolha. Esse é um dos maiores problemas com idiomas que insistem em que tudo deve ser uma classe.
Se você não está realmente passando essas funções falsas, apenas deseja uma função livre / estática.
fonte
Eu sei que estou atrasado para a festa, mas como todos parecem ter esquecido de apontar isso:
Este é um padrão de design bem conhecido chamado: Padrão de Estratégia .
O padrão de estratégia é usado quando existem várias estratégias possíveis para resolver um subproblema. Normalmente, você define uma interface que aplica um contrato em todas as implementações e, em seguida, usa alguma forma de injeção de dependência para fornecer a estratégia concreta para você.
Por exemplo, neste caso, você poderia ter
interface VideoCompressor
e, em seguida, ter várias implementações alternativas, por exemplo,class H264Compressor implements VideoCompressor
eclass XVidCompressor implements VideoCompressor
. Não está claro pelo OP que existe uma interface envolvida, mesmo que não exista, pode ser simplesmente que o autor original tenha deixado a porta aberta para implementar o padrão de estratégia, se necessário. Que por si só também é um bom design.O problema que o OP constantemente se vê instanciando as classes a chamar um método é o problema de ela não usar a injeção de dependência e o padrão de estratégia corretamente. Em vez de instanciar onde você precisar, a classe que contém deve ter um membro com o objeto de estratégia. E esse membro deve ser injetado, por exemplo, no construtor.
Em muitos casos, o padrão de estratégia resulta em classes de interface (como você está mostrando) com apenas um único
doStuff(...)
método.fonte
O que você tem é modular e isso é bom, mas se você agrupasse essas responsabilidades no IVideoProcessor, provavelmente faria mais sentido do ponto de vista do DDD.
Por outro lado, se a divisão, a compactação e a indexação não estivessem relacionadas de alguma forma, eu as manteria como componentes separados.
fonte
É um problema - você está trabalhando a partir do aspecto funcional do design, e não dos dados. O que você realmente tem são três funções independentes que foram executadas por OO.
Por exemplo, você tem uma classe VideoCompressor. Por que você está trabalhando com uma classe projetada para compactar vídeo - por que você não possui uma classe Video com métodos para compactar os dados (vídeo) que cada objeto desse tipo contém?
Ao projetar sistemas OO, é melhor criar classes que representam objetos, em vez de classes que representam atividades que você pode aplicar. Antigamente, as classes eram chamadas de tipos - OO era uma maneira de estender um idioma com suporte para novos tipos de dados. Se você pensar em OO assim, terá uma maneira melhor de projetar suas classes.
EDITAR:
deixe-me tentar me explicar um pouco melhor, imagine uma classe de string que tenha um método concat. Você pode implementar uma coisa em que cada objeto instanciado da classe contenha os dados da string, para poder dizer
mas o OP quer que funcione assim:
agora há lugares em que uma classe pode ser usada para armazenar uma coleção de funções relacionadas, mas isso não é OO, é uma maneira prática de usar os recursos OO de uma linguagem para ajudar a gerenciar melhor sua base de código, mas não é de forma alguma de "OO Design". O objeto nesses casos é totalmente artificial, simplesmente usado assim, porque a linguagem não oferece nada melhor para gerenciar esse tipo de problema. por exemplo. Em linguagens como C #, você usaria uma classe estática para fornecer essa funcionalidade - ela reutiliza o mecanismo de classe, mas não é mais necessário instanciar um objeto apenas para chamar os métodos nele. Você acaba com métodos como o
string.IsNullOrEmpty(mystring)
que eu acho ruim em comparação commystring.isNullOrEmpty()
.Portanto, se alguém perguntar "como faço para projetar minhas classes", recomendo pensar nos dados que a classe conterá, e não nas funções que ela contém. Se você optar por "uma classe é um monte de métodos", você acaba escrevendo o código de estilo "melhor C". (o que não é necessariamente algo ruim se você estiver aprimorando o código C), mas não oferecerá o melhor programa projetado para OO.
fonte
Video
e tende a sobrecarregar essas classes com a funcionalidade, que geralmente termina em código confuso com> 10K LOC por classe. O design avançado de OO divide a funcionalidade em unidades menores como aVideoCompressor
(e permite que umaVideo
classe seja apenas uma classe de dados ou uma fachada para aVideoCompressor
).VideoCompressor
não representa um objeto . Não há nada de errado nisso, apenas mostra o limite do design orientado a objetos.Video
classe, mas a metodologia de compactação não é de forma alguma trivial, então eu realmente dividiria ocompress
método em vários outros métodos privados. Isso é parte do motivo da minha pergunta, depois de ter lido Não criar classes de verbosVideo
classe, mas seria composta de aVideoCompressor
, aVideoSplitter
e outras classes relacionadas, que devem, em boa forma OO, ser classes individuais altamente coesas.O ISP (princípio de segregação de interface) diz que nenhum cliente deve ser forçado a depender dos métodos que não usa. Os benefícios são múltiplos e claros. Sua abordagem respeita totalmente o ISP, e isso é bom.
Uma abordagem diferente também respeitando o ISP é, por exemplo, criar uma interface para cada método (ou conjunto de métodos com alta coesão) e, em seguida, ter uma única classe implementando todas essas interfaces. Se esta é ou não uma solução melhor, depende do cenário. O benefício disso é que, ao usar a injeção de dependência, você pode ter um cliente com diferentes colaboradores (um por cada interface), mas no final todos os colaboradores apontarão para a mesma instância do objeto.
A propósito, você disse
, essas classes parecem ser serviços (e, portanto, sem estado). Você já pensou em fazê-los singletons?
fonte
Sim, há um problema. Mas não grave. Quero dizer, você pode estruturar seu código assim e nada de ruim acontecerá, é sustentável. Mas existem algumas fraquezas nesse tipo de estruturação. Por exemplo, considere se a representação do seu vídeo (no seu caso, está agrupada na classe RawVideo) muda, você precisará atualizar todas as suas classes de operação. Ou considere que você pode ter várias representações de vídeo que variam em tempo de execução. Então você terá que combinar a representação com a classe "operação" específica. Subjetivamente, é irritante arrastar uma dependência para cada operação que você deseja executar na smth. e para atualizar a lista de dependências transmitidas toda vez que você decide que a operação não é mais necessária ou que precisa de nova operação.
Também isso é realmente uma violação do SRP. Algumas pessoas apenas tratam o SRP como um guia para dividir responsabilidades (e levam isso longe ao tratar cada operação de uma responsabilidade distinta), mas esquecem que o SRP também é um guia para agrupar responsabilidades. E, de acordo com as responsabilidades do SRP, as mudanças pelo mesmo motivo devem ser agrupadas para que, se as mudanças acontecerem, sejam localizadas no menor número possível de classes / módulos. Quanto às grandes classes, não é um problema ter vários algoritmos na mesma classe, desde que esses algoritmos estejam relacionados (ou seja, compartilhe algum conhecimento que não deve ser conhecido fora dessa classe). O problema são grandes classes que possuem algoritmos que não estão relacionados de nenhuma maneira e que mudam / variam por diferentes motivos.
fonte