Lutando com o princípio de responsabilidade única

11

Considere este exemplo:

Eu tenho um site. Ele permite que os usuários façam postagens (podem ser qualquer coisa) e adicionem tags que descrevam a postagem. No código, tenho duas classes que representam a postagem e as tags. Vamos chamar essas classes Poste Tag.

Postcuida da criação de postagens, exclusão de postagens, atualização de postagens etc. Tagcuida da criação de tags, exclusão de tags, atualização de tags etc.

Há uma operação que está faltando. A vinculação de tags a postagens. Estou lutando com quem deve fazer esta operação. Poderia se encaixar igualmente bem em qualquer classe.

Por um lado, a Postclasse pode ter uma função que aceita a Tagcomo parâmetro e, em seguida, armazena-a em uma lista de tags. Por outro lado, a Tagclasse pode ter uma função que usa a Postcomo parâmetro e vincula o Taga Post.

O acima é apenas um exemplo do meu problema. Na verdade, estou enfrentando isso com várias classes semelhantes. Poderia se encaixar igualmente bem em ambos. Além de realmente colocar a funcionalidade nas duas classes, quais convenções ou estilos de design existem para me ajudar a resolver esse problema. Estou assumindo que deve haver algo menos do que escolher um?

Talvez colocá-lo nas duas classes seja a resposta correta?

Pássaro nervoso
fonte

Respostas:

11

Como o Código dos Piratas, o SRP é mais uma diretriz do que uma regra, e nem é particularmente bem-formulado. A maioria dos desenvolvedores aceitou as redefinições de Martin Fowler (em Refatoração ) e Robert Martin (em Código Limpo ), sugerindo que uma classe deve ter apenas um motivo para mudar (em oposição a uma responsabilidade).

É uma diretriz boa e sólida (desculpe o trocadilho), mas é quase tão perigoso ficar pendurado nele quanto ignorá-lo.

Se você pode adicionar uma postagem a uma tag e vice-versa, não violou o princípio de responsabilidade única. Ambos ainda têm apenas um motivo para mudar - se a estrutura desse objeto mudar. Alterar a estrutura de um deles não altera a maneira como é adicionado ao outro, portanto você não está adicionando uma nova "responsabilidade" a ele.

Sua decisão final deve realmente ser ditada pela funcionalidade necessária no front-end. Provavelmente, será necessário adicionar uma tag a uma publicação em algum momento; faça algo como o seguinte:

// C-style-language pseudo-code
class Post {
    string _title;
    string _content;
    Date _date;
    List<Tag> _tags;

    Post(string title, string content) {
        _title = title;
        _content = content;
        _date = Now;
        _tags = new List<Tag>();
    }

    Tag[] getTags() {
        return _tags.toArray();
    }

    void addTag(Tag tag) {
        if (_tags.contains(tag)) {
            throw "Cannot add tag twice";
        }

        _tags.Add(tag);
        tag.referencePost(this);
    }

    // more stuff here, obviously
}

class Tag {
    string _name;
    List<Post> _posts;

    Tag(string name) {
        _name = name;
    }

    Post[] getPosts() {
        return _posts.toArray();
    }

    void referencePost(Post post) {
        if (!post.getTags().contains(this) || _posts.contains(post)) {
            throw "Only reference a post by calling Post.addTag()";
        }

        _posts.Add(post);
    }

    // more stuff here too
}

Se, posteriormente, você precisar adicionar Postagens às Tags, adicione um método addPost à classe Tag e um método referenceTag à classe Post. Obviamente, eu os nomeei de forma diferente para que você não cause acidentalmente um estouro de pilha chamando addTag de addPost e addPost de addTag.

pdr
fonte
Acho que o relacionamento entre Tag e Post é muitos-para-muitos; nesse caso, faz sentido para um Tag manter referências a vários Posts. Como você lidaria com isso se mantivesse uma única referência?
Andres F.
@AndresF .: Eu concordo com você, então claramente não escrevi minha resposta muito bem. Eu editei significativamente. (Desculpas à upvoter anterior se isso muda o significado que você viu.)
PDR
6

Não, não em ambos! Deve estar em um só lugar.

O que acho incômodo na sua pergunta é o fato de você dizer " Postcuida da criação de postagens, exclusão de postagens, atualização de postagens" e o mesmo para Tag. Bem, isso não está certo. Postsó pode cuidar da atualização, o mesmo para Tag. Criar e excluir é o trabalho de outra pessoa, externa Poste Tag(vamos chamá-lo Store).

A boa responsabilidade Posté "conhece seu autor, conteúdo e data da última atualização". A boa responsabilidade Tagé "conhece seu nome e propósito (leia-se: descrição)". Uma boa responsabilidade Storeé "conhecer todas as postagens e todas as tags e pode adicionar, remover e pesquisar".

Se você olhar para esses três participantes, quem é naturalmente o que deve ter o conhecimento do relacionamento pós-tag?

(para mim, é o Post, parece natural que "conheça suas tags"; a pesquisa reversa (todas as postagens de uma tag) parece ser o trabalho da loja; embora eu possa estar enganado)

Herby
fonte
Se cada postagem possui uma lista de tags e / ou cada tag possui uma lista de postagens nas quais a inclui, é possível responder facilmente à pergunta "post x include tag y". Existe alguma maneira de responder eficientemente a essa pergunta sem que nenhuma classe assuma a responsabilidade, além de usar algo como a ConditionalWeakTable(assumindo que alguém tenha a sorte de ter uma estrutura onde existe)?
Supercat 23/12
3

Falta um detalhe importante na equação. Por que a tag contém post e vice-versa? A resposta a esta pergunta determina a solução para cada conjunto fornecido.

Em geral, posso pensar em uma situação semelhante. Uma caixa e conteúdo. Uma caixa tem conteúdo, portanto, um relacionamento tem um é apropriado. O conteúdo pode ter uma caixa? Claro, uma caixa com uma caixa. Uma caixa é o conteúdo. Mas o IS-A não é um bom design para todas as caixas. Nesse caso, eu consideraria o padrão decorador. Dessa forma, uma caixa é decorada com o conteúdo em tempo de execução, conforme necessário.

As tags também podem ter Postagens, mas para mim isso não é um relacionamento estático. Em vez disso, poderia ser um relatório de todas as postagens com a referida tag. Nesse caso, é uma nova entidade, não possui-a.

P.Brian.Mackey
fonte
2

Enquanto na teoria coisas assim podem ser aplicadas de qualquer maneira, na prática, quando você se dedica à implementação, uma maneira é quase sempre melhor do que a outra. Meu palpite é que ele se encaixará melhor na Postclasse porque a associação será criada durante a criação ou edição da postagem, quando outras coisas sobre a postagem estiverem mudando ao mesmo tempo.

Além disso, se você estiver associando várias tags e quiser fazer isso em uma atualização do banco de dados, precisará criar algum tipo de lista de todas as tags associadas à mesma postagem antes de fazer a atualização. Essa lista se encaixa muito melhor na Postclasse.

Karl Bielefeldt
fonte
1

Pessoalmente, eu não adicionaria essa funcionalidade a nenhum deles.

Para mim, ambos Poste Tagsão objetos de dados, não deve lidar com a funcionalidade do banco de dados. Eles deveriam simplesmente existir. Eles foram criados para armazenar dados e serem usados ​​por outras partes do seu aplicativo.

Em vez disso, eu teria outra classe responsável por sua lógica de negócios e dados pertencentes à sua página da web. Se sua página estiver exibindo uma postagem e permitindo que os usuários adicionem tags, a classe terá um Postobjeto e conterá funcionalidade para adicionar Tagsa ela Post. Se sua página estiver exibindo tags e permitindo que os usuários adicionem postagens a essas tags, ela conteria um Tagobjeto e teria funcionalidade para adicionar Postsa ela Tag.

Mas sou apenas eu. Se você acha que precisa lidar com a funcionalidade do banco de dados em seus objetos de dados, recomendo a resposta do pdr

Rachel
fonte
0

Ao ler esta pergunta, a primeira coisa que me veio à mente foi um relacionamento de banco de dados Muitos para Muitos . As postagens podem ter muitas tags ... As tags podem ter muitas postagens .... Parece-me que as duas classes precisam da capacidade de gerenciar esse relacionamento até certo ponto.

Do ponto de vista da publicação ...
Se você editar ou criar uma publicação, uma atividade secundária passará a gerenciar os relacionamentos de tags .

  1. Adicionar etiqueta existente à postagem
  2. Revisar tag da publicação

Na IMO, a criação de um TAG completamente novo não pertence aqui.

Do ponto de vista do Tag ...
Você pode criar um Tag sem precisar atribuí-lo a uma Postagem. A única atividade que vejo que envolve a interação com uma publicação é uma função Excluir marca. No entanto, essa função deve ser uma função independente independente.

Isso funcionará apenas se houver uma tabela de vinculação de banco de dados que resolva o relacionamento Muitos para Muitos

Michael Riley - também conhecido por Gunny
fonte