Construindo um projeto com dependências para Emscripten

Neste post irei descrever a construção de um projeto composto por diversas bibliotecas utilizando Emscripten.
No momento, o Emscripten não oferece suporte à construção de bibliotecas compartilhadas, então o primeiro passo é transferir todas as bibliotecas de Compartilhadas para Estáticas. O Emscripten funciona com seus próprios arquivos de inclusão, portanto, o problema com a visibilidade dos arquivos de cabeçalho precisa ser resolvido. Resolvi isso encaminhando um link simbólico do diretório do sistema para o conjunto de ferramentas do Emscripten:

.

ln -s /usr/local/include/FlameSteelFramework $EMSDK/fastcomp/emscripten/system/include/FlameSteelFramework

Se você estiver usando o CMake, será necessário alterar SHARED->STATIC no arquivo CMakeLists.txt do método add_library. Você pode construir uma biblioteca/aplicativo para links estáticos adicionais usando os comandos:

emcmake cmake .
emmake make

Em seguida, você precisará construir o aplicativo principal especificando os arquivos de biblioteca *.a no estágio de vinculação. Não consegui especificar um caminho relativo; a compilação foi concluída corretamente somente após especificar os caminhos completos no arquivo CMakeLists.txt:

elseif(EMSCRIPTEN)
target_link_libraries(${FSEGT_PROJECT_NAME} GL GLEW 
/home/demensdeum/Sources/cube-art-project-bootstrap/cube-art-project/sharedLib/libCubeArtProject.a 
/home/demensdeum/Sources/cube-art-project-bootstrap/FlameSteelFramework/FlameSteelEngineGameToolkitFSGL/libFlameSteelEngineGameToolkitFSGL.a 
/home/demensdeum/Sources/cube-art-project-bootstrap/FlameSteelFramework/FlameSteelEngineGameToolkit/libFlameSteelEngineGameToolkit.a 
/home/demensdeum/Sources/cube-art-project-bootstrap/FlameSteelFramework/FlameSteelCore/libFlameSteelCore.a 
/home/demensdeum/Sources/cube-art-project-bootstrap/FlameSteelFramework/FlameSteelBattleHorn/libFlameSteelBattleHorn.a 
/home/demensdeum/Sources/cube-art-project-bootstrap/FlameSteelFramework/FSGL/libFSGL.a 
/home/demensdeum/Sources/cube-art-project-bootstrap/FlameSteelFramework/FlameSteelCommonTraits/libFlameSteelCommonTraits.a)
else()

Fontes

https://emscripten.org/ docs/compiling/Building-Projects.html#using-libraries

Biblioteca Compartilhada CMake C++

Recentemente, decidi separar todas as partes do FlameSteelFramework em bibliotecas compartilhadas. Depois, mostrarei um exemplo de um arquivo CMakeLists.txt para FlameSteelCore:

cmake_minimum_required(VERSION 3.5)

project (FlameSteelCore)
set(CMAKE_BUILD_TYPE Release)

include_directories(${CMAKE_CURRENT_SOURCE_DIR}/src)

file(GLOB_RECURSE SOURCE_FILES
    "src/FlameSteelCore/*.cpp"
)

add_library(FlameSteelCore SHARED ${SOURCE_FILES})

install(DIRECTORY "${CMAKE_SOURCE_DIR}/src/FlameSteelCore"
        DESTINATION include/FlameSteelFramework
        FILES_MATCHING
        PATTERN "*.h"
)

install(TARGETS FlameSteelCore DESTINATION lib)

Comandos que o CMake executa: coleta todos os arquivos com extensão *.cpp do diretório src/FlameSteelCore/ em uma biblioteca compartilhada, copia todos os cabeçalhos com extensão *.h de src/FlameSteelCore para include/FlameSteelFramework (no meu caso este é /usr/local/include/FlameSteelFramework), copia a lib compartilhada para o diretório lib (/usr/local/lib)
Após a instalação, pode ser necessário atualizar o cache LD – sudoldconfig.
Para compilar e instalar no Ubuntu (se você tiver o conjunto de ferramentas de compilação correto), basta executar os seguintes comandos:

cmake . && make && sudo make install

Para testar o processo de instalação, passo make prefix para a pasta local makeInstallTestPlayground:

cmake -DCMAKE_INSTALL_PREFIX:PATH=/home/demensdeum/makeInstallTestPlayground . && make && make install

Referências

https: //stackoverflow.com/questions/17511496/how-to-create-a-shared-library-with-cmake
https://stackoverflow.com/questions/6003374/what-is-cmake-equivalent-of-configure-prefix-dir-make-all-install

Intérprete da linguagem C++ – Agarrar

Há pouco tempo me deparei com um projeto interessante chamado Cling, um interpretador de linguagem C++ que pode funcionar interativamente a partir do console, entre outras coisas. Você pode visualizar o projeto no seguinte link: https://github.com/root -projeto/agarrar
A instalação do Ubuntu é simples – baixe o arquivo para a versão necessária, descompacte-o, vá para a pasta bin e execute cling no terminal.
Abaixo está um exemplo de carregamento da biblioteca FlameSteelCore, inicializando o objeto, imprimindo o id:

Exceções perdidas do Emscripten e problemas de regex

Exceção perdida

Um recurso interessante do Emscripten: ao iniciar um loop de jogo via emscripten_set_main_loop, você deve lembrar que o tratamento de exceções deve ser adicionado novamente via try catch diretamente no método loop, porque o tempo de execução perde o bloco try catch de fora.
A maneira mais fácil é exibir o texto do erro usando o navegador usando alerta javascript:

            catch (const std::exception &exc)
            {
                const char *errorText = exc.what();
                cout << "Exception: " << errorText << "; Stop execution" << endl;

                EM_ASM_(
                {
                    var errorText = UTF8ToString($0);
                    alert(errorText);

                }, errorText);

                abort();

Expressão regular muito complexa

A implementação padrão de regex pode lançar uma exceção error_complexity se considerar que a expressão regular é muito complexa. Isso acontece na implementação atual do emscripten, então sugiro que você implemente testes para análise por meio de expressões regulares ou use implementações de regex de terceiros.

Construtor de padrões

O padrão Builder pertence a um grupo de padrões cuja existência não é particularmente clara para mim, noto a óbvia redundância disso. Pertence ao grupo de padrões de design generativos. Usado para implementar uma interface simples para criar objetos complexos.

Aplicabilidade

Simplificação da interface. Pode facilitar a criação de um objeto em construtores com um grande número de argumentos e melhorar objetivamente a legibilidade do código.

Exemplo em C++ sem construtor:

auto weapon = new Weapon(“Claws”);
monster->weapon = weapon;
auto health = new MonsterHealth(100);
monster->health = health;

Пример со строителем на C++:

                  .addWeapon(“Claws”)
                  .addHealth(100)
                  .build();

Однако в языках поддерживающих именованные аргументы (named arguments), необходимость использовать именно для этого случая отпадает.

Пример на Swift с использованием named arguments:

let monster = Monster(weapon: “Claws”, health: 100)

Imutabilidade. Usando o construtor, você pode garantir o encapsulamento do objeto criado até a fase final de montagem. Aqui você precisa pensar bem se usar um padrão vai te salvar da alta dinâmica do ambiente em que você trabalha talvez usar o padrão não dê nada, pela simples falta de cultura de uso de encapsulamento entre a equipe de desenvolvimento; .

Interação com componentes em diferentes estágios de criação de objetos. Também utilizando o padrão, é possível garantir a criação passo a passo de um objeto ao interagir com outros componentes do sistema. Provavelmente isso é muito útil (?)

Críticas

É claro que você precisa pensar *com cuidado* se vale a pena estabelecer o uso generalizado do padrão em seu projeto. Linguagens com sintaxe moderna e um IDE avançado eliminam a necessidade de uso do Builder, em termos de melhoria da legibilidade do código (veja o ponto sobre argumentos nomeados)
Esse padrão deveria ter sido usado em 1994, quando o livro GoF foi publicado? Provavelmente sim, porém, a julgar pela base de código-fonte aberto daqueles anos, poucas pessoas o usaram.

Fontes

https://refactoring.guru/ru/design-patterns/builder

Composto Padrão

O padrão Composite refere-se a padrões de design estrutural; em fontes domésticas é conhecido como “Compositor”.
Digamos que estejamos desenvolvendo um aplicativo – álbum de fotos. O usuário pode criar pastas, adicionar fotos e realizar outras manipulações. Definitivamente, você precisa mostrar o número de arquivos em pastas, o número total de todos os arquivos e pastas.
É óbvio que você precisa usar uma árvore, mas como implementar uma arquitetura em árvore com uma interface simples e conveniente? O padrão Composite vem em socorro.

Sheila em Moonducks

A seguir no Diretório implementamos o método dataCount() – percorrendo todos os elementos da matriz de componentes, somando todos os seus dataCount’s.
Tudo está pronto!
Abaixo está um exemplo em Go:
package main

import "fmt"

type component interface {

dataCount() int

}

type file struct {

}

type directory struct {

c []component

}

func (f file) dataCount() int {

return 1

}

func (d directory) dataCount() int {

var outputDataCount int = 0

for _, v := range d.c {
outputDataCount += v.dataCount()
}

return outputDataCount

}

func (d *directory) addComponent(c component) {

d.c = append(d.c, c)

}

func main() {

var f file
var rd directory
rd.addComponent(f)
rd.addComponent(f)
rd.addComponent(f)
rd.addComponent(f)

fmt.Println(rd.dataCount())

var sd directory
sd.addComponent(f)

rd.addComponent(sd)
rd.addComponent(sd)
rd.addComponent(sd)

fmt.Println(sd.dataCount())
fmt.Println(rd.dataCount())

}

Fontes

https://refactoring.guru/ru/design-patterns/ composto

Adaptador de padrão

Benjamín Núñez González

O padrão Adaptador refere-se a padrões de projeto estrutural.

O adaptador fornece conversão de dados/interface entre duas classes/interfaces.

Suponha que estejamos desenvolvendo um sistema para determinar as metas de um comprador em uma loja com base em redes neurais. O sistema recebe um stream de vídeo da câmera de uma loja, identifica os clientes pelo seu comportamento e os classifica em grupos. Tipos de grupos – veio comprar (potencial comprador), só para olhar (espectador), veio roubar algo (ladrão), veio devolver a mercadoria (comprador insatisfeito), veio bêbado/alto (potencial desordeiro).

Como todos os desenvolvedores experientes, encontramos uma rede neural pronta que pode classificar espécies de macacos em uma gaiola com base em um fluxo de vídeo, que o Instituto Zoológico do Zoológico de Berlim gentilmente disponibilizou gratuitamente, treiná-lo novamente em um fluxo de vídeo da loja e obtenha um sistema funcional de última geração .

Há apenas um pequeno problema – o stream de vídeo é codificado no formato mpeg2 e nosso sistema suporta apenas OGG Theora. Não temos o código fonte do sistema, a única coisa que podemos fazer é – alterar o conjunto de dados e treinar a rede neural. O que fazer? Escreva uma classe adaptadora que irá transferir o stream de mpeg2 -> OGG Theora e enviá-lo para a rede neural.

De acordo com o esquema clássico, o padrão envolve cliente, alvo, adaptado e adaptador. O cliente neste caso é uma rede neural que recebe um stream de vídeo no OGG Theora, alvo – a interface com a qual interage, adaptado – interface enviando stream de vídeo em mpeg2, adaptador – converte mpeg2 em OGG Theora e o envia pela interface de destino.

Tudo parece simples?

Fontes

https://ru.wikipedia.org/wiki/Adapter_ (design_pattern)
https://refactoring.guru/ru/design-patterns/adapter

Padrão de delegado

O padrão delegado é um dos principais padrões de design.
Digamos que estamos desenvolvendo um aplicativo para uma barbearia. O aplicativo possui um calendário para seleção de um dia para gravação; ao tocar na data deverá abrir uma lista de barbeiros com opção.
Vamos implementar uma ligação ingênua de componentes do sistema, combinar o calendário e a tela usando ponteiros entre si para implementar uma exibição de lista:


// псевдокод

class BarbershopScreen {
   let calendar: Calendar

   func showBarbersList(date: Date) {
      showSelectionSheet(barbers(forDate: date))
   }
}

class Calendar {
    let screen: BarbershopScreen

    func handleTap(on date: Date) {
        screen.showBarbersList(date: date)
    }
}

Depois de alguns dias, os requisitos mudam; antes de exibir a lista, é necessário mostrar ofertas com opção de serviços (aparar barba, etc.), mas nem sempre, em todos os dias, exceto sábado.
Adicionamos ao calendário uma verificação se é sábado ou não, dependendo disso chamamos o método da lista de barbeiros ou da lista de serviços, para maior clareza irei demonstrar:


// псевдокод

class BarbershopScreen {
   let calendar: Calendar

   func showBarbersList(date: Date) {
      showSelectionSheet(barbers(forDate: date))
   }

   func showOffersList() {
      showSelectionSheet(offers)
   }
}

class Calendar {
    let screen: BarbershopScreen

    func handleTap(on date: Date)  {
        if date.day != .saturday {
             screen.showOffersList()
        }
        else {
             screen.showBarbersList(date: date)
        }
    }
}

Uma semana depois, somos solicitados a adicionar um calendário à tela de feedback e, nesse momento, acontece o primeiro erro arquitetônico!
O que fazer? O calendário está intimamente ligado à tela de marcação de corte de cabelo.
uau! eca! ah ah
Se você continuar trabalhando com essa arquitetura maluca de aplicação, você deve fazer uma cópia de toda a classe de calendário e associar essa cópia à tela de feedback.
Ok, parece bom, então adicionamos mais algumas telas e várias cópias do calendário, e então chegou o momento X. Fomos solicitados a alterar o design do calendário, o que significa que agora precisamos encontrar todas as cópias do calendário e adicionar as mesmas alterações a todas. Essa “abordagem” afeta muito a velocidade de desenvolvimento e aumenta a chance de cometer erros. Como resultado, tais projetos acabam em um estado quebrado, quando até mesmo o autor da arquitetura original não entende mais como funcionam as cópias de suas aulas, e outros hacks adicionados ao longo do caminho desmoronam na hora.
O que precisava ser feito, ou melhor ainda, o que não era tarde demais para começar a fazer? Use o padrão de delegação!
A delegação é uma forma de passar eventos de classe por meio de uma interface comum. Abaixo está um exemplo de delegado para um calendário:

protocol CalendarDelegate {
   func calendar(_ calendar: Calendar, didSelect date: Date)
}

Agora vamos adicionar o código para trabalhar com o delegado ao código de exemplo:


// псевдокод

class BarbershopScreen: CalendarDelegate {
   let calendar: Calendar

   init() {
       calendar.delegate = self
   }

   func calendar(_ calendar: Calendar, didSelect date: Date) {
        if date.day != .saturday {
            showOffersList()
        }
        else {
             showBarbersList(date: date)
        }
   }

   func showBarbersList(date: Date) {
      showSelectionSheet(barbers(forDate: date))
   }

   func showOffersList() {
      showSelectionSheet(offers)
   }
}

class Calendar {
    weak var delegate: CalendarDelegate

    func handleTap(on date: Date)  {
        delegate?.calendar(self, didSelect: date)
    }
}

Como resultado, desvinculamos completamente o calendário da tela ao selecionar uma data no calendário, ele transmite o evento de seleção de data – *delega* processamento de eventos ao assinante; O assinante é a tela.
Que benefícios obtemos com esta abordagem? Agora podemos alterar a lógica do calendário e da tela de forma independente, sem duplicar classes, simplificando ainda mais o suporte; Desta forma, é implementado o “princípio da responsabilidade exclusiva” pela implementação dos componentes do sistema e observado o princípio DRY.
Ao usar a delegação, você pode adicionar, alterar a lógica de exibição das janelas, a ordem de qualquer coisa na tela, e isso não afetará em nada o calendário e outras classes, que objetivamente não devem participar de processos não diretamente relacionados a elas.< br/>Alternativamente, os programadores que não se preocupam muito usam o envio de mensagens através de um barramento comum, sem escrever uma interface separada de protocolo/delegado, onde seria melhor usar a delegação. Escrevi sobre as desvantagens dessa abordagem em um post anterior & # 8211; “Padrão do Observador.”

Fontes

https://refactoring.guru/ru/replace-inheritance -com delegação

Máscara da Morte 1.0!

Hoje existe uma versão de lançamento do jogo Death-Mask, criado do zero junto com o Flame Steel Engine, Flame Steel Engine Game Toolkit e outras bibliotecas.

https://demensdeum.com/games/deathMask/< /p>

No jogo você fará o papel de um cara chamado Revil-Razorback, ele quer se apossar de um artefato que dá vida sem fim – ele quer se apossar de um artefato que dá vida sem fim. Máscara da Morte. Andando por aí em busca, você morrerá nas mãos de drones muitas e muitas vezes até encontrar o que procura.

Atualizações nos modelos 3D e pequenas alterações na jogabilidade serão possíveis em um futuro próximo.

Máscara da Morte – aventura de fantasia cibernética em um labirinto tecnológico sem fim. Prepare-se para morrer!

Padrão Observador

O padrão Observer refere-se a padrões de design comportamentais.
O padrão permite enviar uma mudança no estado de um objeto aos assinantes usando uma interface comum.
Digamos que estamos desenvolvendo um mensageiro para programadores, temos uma tela de chat no aplicativo. Ao receber uma mensagem com o texto “problema” e “erro” ou “algo está errado”, você precisa colorir a tela da lista de erros e a tela de configurações de vermelho.
A seguir, descreverei 2 opções para resolver o problema, a primeira é simples, mas extremamente difícil de suportar, e a segunda é muito mais estável no suporte, mas requer que você vire a cabeça durante a implementação inicial.

Ônibus comum

Todas as implementações do padrão contêm o envio de mensagens quando os dados são alterados, a assinatura de mensagens e o processamento adicional em métodos. A opção de barramento compartilhado contém um único objeto (geralmente um singleton) que despacha mensagens aos destinatários.
A simplicidade de implementação é a seguinte:

  1. O objeto envia uma mensagem abstrata para o barramento compartilhado
  2. Outro objeto inscrito no barramento compartilhado captura a mensagem e decide se deve processá-la ou não.

Uma das opções de implementação disponíveis na Apple (subsistema NSNotificationCenter), adicionou correspondência do cabeçalho da mensagem ao nome do método que é chamado pelo destinatário na entrega.
A maior desvantagem desta abordagem – Se você alterar ainda mais a mensagem, primeiro precisará lembrar e depois editar manualmente todos os locais para onde ela é processada e enviada. É um caso de implementação inicial rápida, seguida de suporte longo e complexo que requer uma base de conhecimento para operação correta.

Delegado multicast

Nesta implementação, faremos a classe delegada multicast final, assim como no caso de um barramento compartilhado, os objetos podem se inscrever nele para receber “mensagens” ou “eventos”, mas o trabalho de análise e filtragem de mensagens é feito; não atribuído aos objetos. Em vez disso, as classes de assinantes devem implementar os métodos multicast do delegado com os quais são notificados.
Isso é implementado usando interfaces/protocolos delegados; quando a interface geral muda, a aplicação não será mais construída, momento em que será necessário refazer todos os locais para processamento de uma determinada mensagem, sem a necessidade de manter uma base de conhecimento separada. para lembrar desses lugares. O compilador é seu amigo.
Esta abordagem aumenta a produtividade da equipe, pois não há necessidade de escrever ou armazenar documentação, não há necessidade de um novo desenvolvedor tentar entender como uma mensagem e seus argumentos são processados, ao invés disso eles trabalham com uma interface conveniente e compreensível , é assim que o paradigma de documentação por meio de código é implementado.
O delegado multicast em si é baseado no padrão de delegado, sobre o qual escreverei no próximo post.

Fontes

https://refactoring.gu/ru/design-patterns/observer

Padrão de proxy

O padrão Proxy refere-se a padrões de projeto estruturais.
O padrão descreve a técnica de trabalhar com uma classe através de uma camada de classe – procuração. Um proxy permite alterar a funcionalidade da classe original, com a capacidade de preservar o comportamento original, enquanto mantém a interface da classe original.
Vamos imaginar a situação – em 2015, um dos países da Europa Ocidental decide registar todos os pedidos aos sites dos utilizadores do país, de forma a melhorar as estatísticas e a compreensão aprofundada dos sentimentos políticos dos cidadãos.
Imaginemos o pseudocódigo de uma implementação ingênua do gateway que os cidadãos utilizam para acessar a Internet:

class InternetRouter {

    private let internet: Internet

    init(internet: Internet) {
        self.internet = internet
    }

    func handle(request: Request, from client: Client) -> Data {
        return self.internet.handle(request)
    }

}

No código acima, criamos uma classe de roteador de Internet com um ponteiro para um objeto que fornece acesso à Internet. Quando um cliente faz uma solicitação de site, retornamos uma resposta da Internet.

Usando o padrão Proxy e o antipadrão singleton, adicionaremos funcionalidade para registrar o nome e URL do cliente:

class InternetRouterProxy {

    private let internetRouter: InternetRouter

    init(internet: Internet) {
        self.internetRouter = InternetRouter(internet: internet)
    }

    func handle(request: Request, from client: Client) -> Data {

        Logger.shared.log(“Client name: \(client.name), requested URL: \(request.URL)”)

        return self.internetRouter.handle(request: request, from: client)
    }

}

Devido à preservação da interface InternetRouter original na classe proxy InternetRouterProxy, basta substituir a classe de inicialização do InternerRouter pelo seu proxy, não sendo necessárias mais alterações na base de código.

Fontes

https://refactoring.guru/ru/design-patterns/ proxy

Máscara Mortal Beta Selvagem

O jogo Death-Mask entra em status de beta público (wild beta)
A tela do menu principal do jogo foi redesenhada, foi adicionada uma visão da zona azul do labirinto tecnológico, com uma música agradável de fundo.

Em seguida, pretendo retrabalhar o controlador de jogo, adicionar movimentos suaves como nos jogos de tiro antigos, modelos 3D de caixas, armas, inimigos de alta qualidade, a capacidade de passar para outros níveis do labirinto tecnológico não apenas através de portais ( elevadores, portas, quedas por buracos no chão, buracos nas paredes), acrescentarei um pouco de variedade ao ambiente do labirinto gerado. Também trabalharei no equilíbrio do jogo.
A animação do esqueleto será adicionada como uma fase de polimento antes do lançamento.< /p>

Protótipo de Padrão

O padrão de protótipo pertence ao grupo de padrões de projeto generativos.
Digamos que estamos desenvolvendo aplicativos de namoro Tender De acordo com nosso modelo de negócio, temos a oportunidade paga de fazer cópias do seu próprio perfil, alterando automaticamente o nome e a ordem das fotos nos locais. Isso foi feito para que o usuário tivesse a oportunidade de manter vários perfis ao mesmo tempo com um conjunto diferente de amigos no aplicativo.
Ao clicar no botão para criar uma cópia do perfil, precisamos implementar a cópia do perfil, gerar automaticamente um nome e reordenar as fotos.
Implementação ingênua de pseudocódigo:

fun didPressOnCopyProfileButton() {
    let profileCopy = new Profile()
    profileCopy.name = generateRandomName()
    profileCopy.age = profile.age
    profileCopy.photos = profile.photos.randomize()
    storage.save(profileCopy)
}

Agora vamos imaginar que outros membros da equipe copiaram e colaram o código de cópia ou o criaram do zero e, depois disso, um novo campo foi adicionado – gosta. Este campo armazena o número de curtidas do perfil, agora você precisa atualizar *todos* os locais onde a cópia ocorre manualmente adicionando um novo campo. É muito demorado e difícil manter o código, bem como testar.
Para resolver este problema, o padrão de design Prototype foi inventado. Vamos criar um protocolo de cópia geral, com um método copy() que retorna uma cópia de um objeto com os campos necessários. Depois de alterar os campos da entidade, você só precisará atualizar um método copy(), em vez de pesquisar e atualizar manualmente todos os locais que contêm o código de cópia.

Fontes

https://refactoring.guru/ru/design-patterns/prototype

Máquina de estado e padrão Condição

Neste artigo irei descrever o uso da máquina de estados (State Machine), mostrar uma implementação simples, uma implementação utilizando o padrão State. Vale ressaltar que não é desejável utilizar o padrão Estado se houver menos de três estados, pois isso geralmente leva a uma complexidade desnecessária na legibilidade do código e a problemas de suporte associados. tudo deve ser feito com moderação.

MEAACT PHOTO / STUART PRICE.

Senhor das Bandeiras

Suponha que estejamos desenvolvendo uma tela de player de vídeo para o sistema de mídia de uma aeronave civil, o player deve ser capaz de carregar um stream de vídeo, reproduzi-lo, permitir ao usuário interromper o processo de download, retroceder e realizar outras operações usuais para um jogador.
Digamos que o player armazenou em cache o próximo pedaço do stream de vídeo, verificou se há pedaços suficientes para reprodução, começou a reproduzir o fragmento para o usuário e ao mesmo tempo continua baixando o próximo.
Neste momento, o usuário retrocede até o meio do vídeo, ou seja, agora é necessário interromper a reprodução do fragmento atual e iniciar o carregamento a partir de uma nova posição. Porém, existem situações em que isso não pode ser feito – o usuário não pode controlar a reprodução do fluxo de vídeo enquanto está vendo um vídeo sobre segurança aérea. Vamos verificar o sinalizador isSafetyVideoPlaying para verificar esta situação.
O sistema também deve ser capaz de pausar o vídeo atual e transmitir um alerta do capitão e da tripulação do navio através do player. Vamos adicionar outro sinalizador isAnnouncementPlaying. Além disso, há um requisito para não pausar a reprodução enquanto exibe ajuda sobre como trabalhar com o player. Outro sinalizador éHelpPresenting.

Pseudocódigo de exemplo do reprodutor de mídia:

class MediaPlayer {

    public var isHelpPresenting = false
    public var isCaching = false
    public var isMediaPlaying: Bool = false
    public var isAnnouncementPlaying = false
    public var isSafetyVideoPlaying = false

    public var currentMedia: Media = null

    fun play(media: Media) {

        if isMediaPlaying == false, isAnnouncementPlaying == false, isSafetyVideoPlaying == false {

            if isCaching == false {
                if isHelpPresenting == false {
                    media.playAfterHelpClosed()
                }
                else {
                    media.playAfterCaching()
                }
            }
    }

    fun pause() {
        if isAnnouncementPlaying == false, isSafetyVideoPlaying == false {
            currentMedia.pause()
        }
    }
}

O exemplo acima é difícil de ler e manter devido à alta variabilidade (entropia). Este exemplo é baseado em minha experiência trabalhando com a base de código de *muitos* projetos que não usavam uma máquina de estado.
Cada checkbox deve “controlar” especificamente os elementos da interface e da lógica de negócio da aplicação, o desenvolvedor, adicionando outro checkbox, deve ser capaz de fazer malabarismos com eles, verificando e verificando tudo várias vezes com todas as opções possíveis.
Substituindo na fórmula “2 ^ número de caixas de seleção” você pode obter 2 ^ 6 = 64 opções de comportamento do aplicativo para apenas 6 caixas de seleção, todas essas combinações de caixas de seleção precisarão ser marcadas e mantidas manualmente.
Do lado do desenvolvedor, adicionar novas funcionalidades a esse sistema é assim:
– Precisamos adicionar a capacidade de mostrar a página do navegador da companhia aérea, e isso deve minimizar a semelhança com os filmes se os membros da tripulação anunciarem algo.
– Ok, eu farei isso. (Ah, droga, terei que adicionar outra bandeira e verificar novamente todos os locais onde as bandeiras se cruzam, há muitas coisas que precisam ser alteradas!)

Também é um ponto fraco do sistema de bandeiras – fazer alterações no comportamento do aplicativo. É muito difícil imaginar como alterar o comportamento de forma rápida/flexível com base em sinalizadores, se depois de alterar apenas um sinalizador você tiver que verificar tudo novamente. Esta abordagem ao desenvolvimento leva a muitos problemas, perda de tempo e dinheiro.

Entre na máquina

Se você observar atentamente os sinalizadores, poderá entender que, na verdade, estamos tentando processar processos específicos que ocorrem no mundo real. Nós os listamos: modo normal, exibição de vídeo de segurança, transmissão de mensagem do capitão ou tripulantes. Para cada processo é conhecido um conjunto de regras que alteram o comportamento da aplicação.
De acordo com as regras do padrão de máquina de estado (máquina de estado), listaremos todos os processos como estados no enum, adicionaremos um conceito como estado ao código do jogador, implementaremos comportamento baseado em estado removendo combinações nos sinalizadores. Assim, reduziremos as opções de testes exatamente ao número de estados.

Pseudocódigo:

enum MediaPlayerState {
	mediaPlaying,
	mediaCaching,
	crewSpeaking,
	safetyVideoPlaying,
	presentingHelp
}

class MediaPlayer {
	fun play(media: Media) {
		media.play()
	}

	func pause() {
		media.pause()
	}
}

class MediaPlayerStateMachine {
	public state: MediaPlayerState
	public mediaPlayer: MediaPlayer
	public currentMedia: Media

	//.. init (mediaPlayer) etc

	public fun set(state: MediaPlayerState) {
		switch state {
			case mediaPlaying:
				mediaPlayer.play(currentMedia)
			case mediaCaching, crewSpeaking,
			safetyVideoPlaying, presentingHelp:
				mediaPlayer.pause()
		}
	}
}

A grande diferença entre um sistema de sinalizadores e uma máquina de estado é o funil lógico de comutação de estado no método set(state: ..), que permite traduzir a compreensão humana do estado em código de programa, sem ter que jogar lógica jogos de conversão de bandeiras em estados quando houver suporte adicional ao código.

Estado do padrão

A seguir mostrarei a diferença entre a implementação ingênua da máquina de estados e o padrão de estados. Vamos imaginar que precisávamos adicionar 10 estados; como resultado, a classe da máquina de estados crescerá até o tamanho de um objeto divino, o que será difícil e caro de manter. Claro, esta implementação é melhor do que a implementação de flag (com o sistema de flag, o desenvolvedor irá atirar em si mesmo primeiro, e se não, então vendo 2 ^ 10 = 1024 variações, o QA se enforcará, mas se ambos *não notar* a complexidade da tarefa, então o usuário cuja aplicação é simples perceberá que se recusará a trabalhar com uma determinada combinação de flags)
Se houver um grande número de estados, é necessário utilizar o padrão State.
Vamos adicionar um conjunto de regras ao protocolo do Estado:

protocol State {
    func playMedia(media: Media, context: MediaPlayerContext)
    func shouldCacheMedia(context: MediaPlayerContext)
    func crewSpeaking(context: MediaPlayerContext)
    func safetyVideoPlaying(context:MediaPlayerContext)
    func presentHelp(context: MediaPlayerContext)
}

Vamos mover a implementação do conjunto de regras para estados separados, por exemplo, o código para um estado:

class CrewSpeakingState: State {
	func playMedia(context: MediaPlayerContext) {
		showWarning(“Can’ t play media - listen to announce!”)
	}

	func mediaCaching(context: MediaPlayerContext) {
		showActivityIndicator()
	}

	func crewSpeaking(context: MediaPlayerContext) {
		set(volume: 100)
	}

	func safetyVideoPlaying(context: MediaPlayerContext) {
		set(volume: 100)
	}

	func presentHelp(context: MediaPlayerContext) {
		showWarning(“Can’ t present help - listen to announce!”)
	}
}

A seguir, vamos criar um contexto com o qual cada estado funcionará e integrar a máquina de estados:

final class MediaPlayerContext {
	private
	var state: State

	public fun set(state: State) {
		self.state = state
	}

	public fun play(media: Media) {
		state.play(media: media, context: this)
	}

	…
	остальные возможные события
}

Os componentes da aplicação trabalham com o contexto através de métodos públicos; os próprios objetos de estado decidem de qual estado fazer a transição usando a máquina de estado dentro do contexto.
Assim, implementamos a decomposição do Objeto Deus, manter um estado de mudança será muito mais fácil, graças ao compilador rastreando as alterações no protocolo, reduzindo a complexidade de compreensão dos estados devido à redução no número de linhas de código, e focando em resolver um problema de estado específico. Agora você também pode compartilhar o trabalho em equipe, entregando a implementação de um estado específico aos membros da equipe, sem se preocupar com a necessidade de “resolver” conflitos, o que acontece quando se trabalha com uma grande classe de máquina de estado.

Fontes

https://refactoring.guru/ru/design-patterns/state

Animação esquelética (Parte 1 – shader)

Neste artigo descreverei minha compreensão da animação esquelética, que é usada em todos os motores 3D modernos para animar personagens, ambientes de jogos, etc.
Começarei a descrição com a parte mais tangível – sombreador de vértice, pois todo o caminho de cálculo, por mais complexo que seja, termina com a transferência dos dados preparados para exibição para o sombreador de vértice.

A animação do esqueleto, após ser processada na CPU, vai para o vertex shader.
Deixe-me lembrá-lo da fórmula para vértice sem animação esquelética:
gl_Position = projeçãoMatrix * viewMatrix * modelMatrix * vértice;
Para quem não entende como surgiu essa fórmula, pode ler meu artigo que descreve o princípio de trabalhar com matrizes para exibição de conteúdo 3D no contexto do OpenGL.
Quanto ao resto – fórmula para implementar animação esquelética:
” vec4 animadoVertex = bone0matrix * vértice * bone0weight +”
“bone1matrix * vértice * bone1weight +”
“bone2matrix * vértice * bone2weight +”
“bone3matrix * vértice * bone3weight;\n”
” gl_Position = projeçãoMatrix * viewMatrix * modelMatrix * animadoVertex;\n”

Ou seja, multiplicamos a matriz de transformação óssea final pelo vértice e pelo peso desta matriz em relação ao vértice. Cada vértice pode ser animado por 4 ossos, a força do impacto é regulada pelo parâmetro peso do osso, a soma dos impactos deve ser igual a um.
O que fazer se menos de 4 ossos afetarem o vértice? Precisamos dividir o peso entre eles e fazer com que o impacto do restante seja igual a zero.
Matematicamente, multiplicar um peso por uma matriz é chamado de “multiplicação escalar de matriz”. Multiplicar por um escalar permite resumir o efeito das matrizes no vértice resultante.

As próprias matrizes de transformação óssea são transmitidas como uma matriz. Além disso, a matriz contém matrizes para todo o modelo como um todo, e não para cada malha separadamente.

Mas para cada vértice as seguintes informações são transmitidas separadamente:
– Índice da matriz que afeta o vértice
– Peso da matriz que afeta o vértice
Mais de um osso é transmitido, geralmente é utilizado o efeito de 4 ossos no vértice.
Além disso, a soma dos pesos dos 4 dados deve ser sempre igual a um.
A seguir, vamos ver como fica no shader.
Matriz matricial:
“bonesMatrices mat4 uniformes[kMaxBones];”

Informações sobre o efeito de 4 ossos em cada vértice:
“atributo vec2 bone0info;”
“atributo vec2 bone1info;”
“atributo vec2 bone2info;”
“atributo vec2 bone3info;”

vec2 – na coordenada X armazenamos o índice do osso (e convertemos para int no shader), na coordenada Y armazenamos o peso do impacto do osso no vértice. Por que você tem que transmitir esses dados em um vetor bidimensional? Porque o GLSL não suporta a passagem de estruturas legíveis em C com campos válidos para o shader.

A seguir darei um exemplo de obtenção das informações necessárias de um vetor para posterior substituição na fórmula animadoVertex:

“int bone0Index = int(bone0info.x);”
“float bone0weight = bone0info.y;”
“mat4 bone0matrix = boneMatrices[bone0Index];”

“int bone1Index = int(bone1info.x);”
“float bone1weight = bone1info.y;”
“mat4 bone1matrix = boneMatrices[bone1Index];”

“int bone2Index = int(bone2info.x);”
“float bone2weight = bone2info.y;”
“mat4 bone2matrix = boneMatrices[bone2Index];”

“int bone3Index = int(bone3info.x);”
“float bone3weight = bone3info.y;”
“mat4 bone3matrix = boneMatrices[bone3Index];”

Agora a estrutura de vértices preenchida na CPU deve ficar assim:
x, y, z, u, v, bone0index, bone0weight, bone1index, bone1weight, bone2index, bone2weight, bone3index, bone3weight

A estrutura do buffer de vértice é preenchida uma vez durante o carregamento do modelo, mas as matrizes de transformação são transferidas da CPU para o shader em cada quadro de renderização.

Nas partes restantes, descreverei o princípio de cálculo da animação na CPU, antes de transferi-la para o vertex shader, descreverei a árvore de nós ósseos, percorrendo a hierarquia de animação-modelo-nós-malha, matriz interpolação.

Fontes

http://ogldev.atspace.co. pt/www/tutorial38/tutorial38.html

Código fonte

https://gitlab.com/demensdeum/skeletal-animation

Método de modelo

O método padrão refere-se a padrões de design comportamentais. O padrão descreve uma maneira de substituir parte da lógica de uma classe sob demanda, deixando a parte geral inalterada para os descendentes.

Cuban Cars

Suponha que estejamos desenvolvendo um banco cliente, considere a tarefa de desenvolver um módulo de autorização – o usuário deve ser capaz de fazer login no aplicativo usando dados de login abstratos.
O módulo de autorização deve ser multiplataforma, suportando diferentes tecnologias de autorização e armazenando dados criptografados de diferentes plataformas. Para implementar o módulo, escolhemos a linguagem Kotlin multiplataforma, usando a classe abstrata (protocolo) do módulo de autorização, escreveremos uma implementação para o telefone MyPhone:

class MyPhoneSuperDuperSecretMyPhoneAuthorizationStorage {
    fun loginAndPassword() : Pair {
        return Pair("admin", "qwerty65435")
    }
}

class ServerApiClient {
    fun authorize(authorizationData: AuthorizationData) : Unit {
        println(authorizationData.login)
        println(authorizationData.password)
        println("Authorized")
    }
}

class AuthorizationData {
    var login: String? = null
    var password: String? = null
}

interface AuthorizationModule {
    abstract fun fetchAuthorizationData() : AuthorizationData
    abstract fun authorize(authorizationData: AuthorizationData)
}

class MyPhoneAuthorizationModule: AuthorizationModule {
    
    override fun fetchAuthorizationData() : AuthorizationData {
        val loginAndPassword = MyPhoneSuperDuperSecretMyPhoneAuthorizationStorage().loginAndPassword()
        val authorizationData = AuthorizationData()
        authorizationData.login = loginAndPassword.first
        authorizationData.password = loginAndPassword.second
        
        return authorizationData
    }
    
    override fun authorize(authorizationData: AuthorizationData) {
        ServerApiClient().authorize(authorizationData)
    }
    
}

fun main() {
    val authorizationModule = MyPhoneAuthorizationModule()
    val authorizationData = authorizationModule.fetchAuthorizationData()
    authorizationModule.authorize(authorizationData)
}

Agora para cada telefone/plataforma teremos que duplicar o código de envio de autorização ao servidor, isso é uma violação do princípio DRY. O exemplo acima é muito simples, em classes mais complexas haverá ainda mais duplicação. Para eliminar a duplicação de código, você deve usar o padrão Template Method.
Vamos mover as partes comuns do módulo para métodos imutáveis ​​e transferir a funcionalidade de transferência de dados criptografados para classes de plataforma específicas:

class MyPhoneSuperDuperSecretMyPhoneAuthorizationStorage {
    fun loginAndPassword() : Pair {
        return Pair("admin", "qwerty65435")
    }
}

class ServerApiClient {
    fun authorize(authorizationData: AuthorizationData) : Unit {
        println(authorizationData.login)
        println(authorizationData.password)
        println("Authorized")
    }
}

class AuthorizationData {
    var login: String? = null
    var password: String? = null
}

interface AuthorizationModule {
    abstract fun fetchAuthorizationData() : AuthorizationData
    
    fun authorize(authorizationData: AuthorizationData) {
        ServerApiClient().authorize(authorizationData)
    }
}

class MyPhoneAuthorizationModule: AuthorizationModule {
    
    override fun fetchAuthorizationData() : AuthorizationData {
        val loginAndPassword = MyPhoneSuperDuperSecretMyPhoneAuthorizationStorage().loginAndPassword()
        val authorizationData = AuthorizationData()
        authorizationData.login = loginAndPassword.first
        authorizationData.password = loginAndPassword.second
        
        return authorizationData
    }
    
}

fun main() {
    val authorizationModule = MyPhoneAuthorizationModule()
    val authorizationData = authorizationModule.fetchAuthorizationData()
    authorizationModule.authorize(authorizationData)
}

Fontes

https://refactoring.guru/ru/design- padrões/método de modelo

Código fonte

https://gitlab.com/demensdeum/patterns/< /p>

Ponte Padrão

O padrão Bridge refere-se a padrões de projeto estrutural. Ele permite abstrair a implementação da lógica de classe movendo a lógica para uma classe abstrata separada. Parece simples, certo?

Suponha que implementemos um bot de spam que seja capaz de enviar mensagens para diferentes tipos de mensageiros.
Nós o implementamos usando um protocolo comum:

protocol User {
    let token: String
    let username: String
}

protocol Messenger {
    var authorize(login: String, password: String)
    var send(message: String, to user: User)
}

class iSeekUUser: User {
    let token: String
    let username: String
}

class iSeekU: Messenger {

    var authorizedUser: User?
    var requestSender: RequestSender?
    var requestFactory: RequestFactory?

    func authorize(login: String, password: String) {
        authorizedUser = requestSender?.perform(requestFactory.loginRequest(login: login, password: password))
    }
    
    func send(message: String, to user: User) {
        requestSender?.perform(requestFactory.messageRequest(message: message, to: user)
    }
}

class SpamBot {
    func start(usersList: [User]) {
        let iSeekUMessenger = iSeekU()
        iSeekUMessenger.authorize(login: "SpamBot", password: "SpamPassword")
        
        for user in usersList {
            iSeekUMessennger.send(message: "Hey checkout demensdeum blog! http://demensdeum.com", to: user)
        }
    }
}

Agora vamos imaginar o lançamento de um protocolo novo e mais rápido para envio de mensagens para o mensageiro iSekU. Para adicionar um novo protocolo, você precisará duplicar a implementação do bot iSekU, alterando apenas uma pequena parte dele. Não está claro por que fazer isso se apenas uma pequena parte da lógica da classe mudou. Com esta abordagem, o princípio DRY é violado; com o desenvolvimento do produto, a falta de flexibilidade se fará sentir através de erros e atrasos na implementação de novos recursos.
Vamos mover a lógica do protocolo para uma classe abstrata, implementando assim o padrão Bridge:

protocol User {
    let token: String
    let username: String
}

protocol Messenger {
    var authorize(login: String, password: String)
    var send(message: String, to user: User)
}

protocol MessagesSender {
    func send(message: String, to user: User)
}

class iSeekUUser: User {
    let token: String
    let username: String
}

class iSeekUFastMessengerSender: MessagesSender {
    func send(message: String, to user: User) {
        requestSender?.perform(requestFactory.messageRequest(message: message, to: user)
    }
}

class iSeekU: Messenger {

    var authorizedUser: User?
    var requestSender: RequestSender?
    var requestFactory: RequestFactory?
    var messagesSender: MessengerMessagesSender?

    func authorize(login: String, password: String) {
        authorizedUser = requestSender?.perform(requestFactory.loginRequest(login: login, password: password))
    }
    
    func send(message: String, to user: User) {
        messagesSender?.send(message: message, to: user)
    }
}

class SpamBot {

    var messagesSender: MessagesSender?

    func start(usersList: [User]) {
        let iSeekUMessenger = iSeekU()
        iSeekUMessenger.authorize(login: "SpamBot", password: "SpamPassword")
        
        for user in usersList {
            messagesSender.send(message: "Hey checkout demensdeum blog! http://demensdeum.com", to: user)
        }
    }
}

Uma das vantagens desta abordagem é, sem dúvida, a capacidade de expandir a funcionalidade da aplicação escrevendo plugins/bibliotecas que implementam lógica abstraída sem alterar o código da aplicação principal.
Qual é a diferença com o padrão Estratégia? Ambos os padrões são muito semelhantes, no entanto, Estratégia descreve *algoritmos* de comutação, enquanto Bridge permite alternar grandes partes de *qualquer lógica complexa*.

Fontes

https://refactoring.guru/ru/design-patterns/bridge

Código fonte

https://gitlab.com/demensdeum/patterns/p>

Padrão de Cadeia de Responsabilidade

Cadeia de responsabilidade refere-se a padrões de design comportamental.


Ganna Dolbieva

A produtora Jah-Pictures fez um documentário sobre rastafáris comunistas da Libéria chamado “Red Dawn of Marley”. O filme é muito longo (8 horas), interessante, mas antes de ser lançado descobriu-se que em alguns países os planos e frases do filme podem ser considerados heresia e não receberão licença de distribuição. Os produtores do filme decidem recortar do filme momentos que contenham frases duvidosas, de forma manual e automática. É necessária uma dupla verificação para que os representantes do distribuidor não sejam simplesmente fuzilados em alguns países em caso de erro durante a inspeção e instalação manual.
Os países estão divididos em quatro grupos – países sem censura, com censura moderada, média e muito rigorosa. É tomada a decisão de usar redes neurais para classificar o nível de heresia no fragmento assistido do filme. Para o projeto, são adquiridos neurônios de última geração, caríssimos, treinados para diferentes níveis de censura, tarefa do desenvolvedor – tarefa do desenvolvedor. quebrar o filme em fragmentos e transmiti-los através de uma cadeia de redes neurais, de livre a estrita, até que uma delas detecte heresia, então o fragmento é transferido para revisão manual para posterior edição. É impossível passar por todos os neurônios, porque seu trabalho exige muito poder de computação (afinal, ainda temos que pagar pela eletricidade), basta parar no primeiro que funcionar.
Implementação ingênua de pseudocódigo:

import StateOfArtCensorshipHLNNClassifiers

protocol MovieCensorshipClassifier {
    func shouldBeCensored(movieChunk: MovieChunk) -> Bool
}

class CensorshipClassifier: MovieCensorshipClassifier {

    let hnnclassifier: StateOfArtCensorshipHLNNClassifier

    init(_ hnnclassifier: StateOfArtCensorshipHLNNClassifier) {
        self.hnnclassifier = hnnclassifier
    }
    
    func shouldBeCensored(_ movieChunk: MovieChunk) -> Bool {
        return hnnclassifier.shouldBeCensored(movieChunk)
    }
}

let lightCensorshipClassifier = CensorshipClassifier(StateOfArtCensorshipHLNNClassifier("light"))
let normalCensorshipClassifier = CensorshipClassifier(StateOfArtCensorshipHLNNClassifier("normal"))
let hardCensorshipClassifier = CensorshipClassifier(StateOfArtCensorshipHLNNClassifier("hard"))

let classifiers = [lightCensorshipClassifier, normalCensorshipClassifier, hardCensorshipClassifier]

let movie = Movie("Red Jah rising")
for chunk in movie.chunks {
    for classifier in classifiers {
        if classifier.shouldBeCensored(chunk) == true {
            print("Should censor movie chunk: \(chunk), reported by \(classifier)")
        }
   }
}

Em geral, a solução com um array de classificadores não é tão ruim, porém! Vamos imaginar que não podemos criar um array, temos a oportunidade de criar apenas uma entidade classificadora, que já determina o tipo de censura para um fragmento de filme. Tais restrições são possíveis ao desenvolver uma biblioteca que expanda a funcionalidade da aplicação (plugin).
Vamos usar o padrão decorador – Vamos adicionar uma referência ao próximo classificador da cadeia à classe do classificador e interromper o processo de verificação na primeira classificação bem-sucedida.
Assim, implementamos o padrão Cadeia de Responsabilidade:

import StateOfArtCensorshipHLNNClassifiers

protocol MovieCensorshipClassifier {
    func shouldBeCensored(movieChunk: MovieChunk) -> Bool
}

class CensorshipClassifier: MovieCensorshipClassifier {

    let nextClassifier: CensorshipClassifier?
    let hnnclassifier: StateOfArtCensorshipHLNNClassifier

    init(_ hnnclassifier: StateOfArtCensorshipHLNNClassifier, nextClassifier: CensorshipClassifiers?) {
            self.nextClassifier = nextClassifier
            self.hnnclassifier = hnnclassifier
    }
    
    func shouldBeCensored(_ movieChunk: MovieChunk) -> Bool {
        let result = hnnclassifier.shouldBeCensored(movieChunk)
        
        print("Should censor movie chunk: \(movieChunk), reported by \(self)")
        
        if result == true {
                return true
        }
        else {
                return nextClassifier?.shouldBeCensored(movieChunk) ?? false
        }
    }
}

let censorshipClassifier = CensorshipClassifier(StateOfArtCensorshipHLNNClassifier("light"), nextClassifier: CensorshipClassifier(StateOfArtCensorshipHLNNClassifier("normal", nextClassifier: CensorshipClassifier(StateOfArtCensorshipHLNNClassifier("hard")))))

let movie = Movie("Red Jah rising")
for chunk in movie.chunks {
    censorshipClassifier.shouldBeCensored(chunk)
}

Referências

https://refactoring.guru/ru/ padrões de design/cadeia de responsabilidade

Código Fonte

https://gitlab.com/demensdeum/patterns/< /p>

Decorador de padrões

O padrão Decorator refere-se a padrões de projeto estrutural.

O decorador é usado como alternativa à herança para estender a funcionalidade das classes.
Existe a tarefa de expandir a funcionalidade do aplicativo dependendo do tipo de produto. O cliente exige três tipos de produto – Básico, Profissional, Final.
Básico– conta o número de caracteres, Profissional – recursos Basic + imprime texto em letras maiúsculas, Ultimate – Básico + Profissional + imprime texto dizendo ULTIMATE.
Nós o implementamos usando herança:

protocol Feature {
	func textOperation(text: String)
}

class BasicVersionFeature: Feature {
	func textOperation(text: String) {
		print("\(text.count)")
	}
}

class ProfessionalVersionFeature: BasicVersionFeature {
	override func textOperation(text: String) {
		super.textOperation(text: text)
		print("\(text.uppercased())")
	}
}

class UltimateVersionFeature: ProfessionalVersionFeature {
	override func textOperation(text: String) {
		super.textOperation(text: text)
		print("ULTIMATE: \(text)")
	}
}

let textToFormat = "Hello Decorator"

let basicProduct = BasicVersionFeature()
basicProduct.textOperation(text: textToFormat)

let professionalProduct = ProfessionalVersionFeature()
professionalProduct.textOperation(text: textToFormat)

let ultimateProduct = UltimateVersionFeature()
ultimateProduct.textOperation(text: textToFormat)

Agora existe a necessidade de implementação do produto “Ultimate Light” – Basic + Ultimate, mas sem os recursos da versão Professional. O primeiro OH! acontece, porque… você terá que criar uma classe separada para uma tarefa tão simples e duplicar o código.
Vamos continuar a implementação usando herança:

protocol Feature {
	func textOperation(text: String)
}

class BasicVersionFeature: Feature {
	func textOperation(text: String) {
		print("\(text.count)")
	}
}

class ProfessionalVersionFeature: BasicVersionFeature {
	override func textOperation(text: String) {
		super.textOperation(text: text)
		print("\(text.uppercased())")
	}
}

class UltimateVersionFeature: ProfessionalVersionFeature {
	override func textOperation(text: String) {
		super.textOperation(text: text)
		print("ULTIMATE: \(text)")
	}
}

class UltimateLightVersionFeature: BasicVersionFeature {
	override func textOperation(text: String) {
		super.textOperation(text: text)
		print("ULTIMATE: \(text)")	
	}
}

let textToFormat = "Hello Decorator"

let basicProduct = BasicVersionFeature()
basicProduct.textOperation(text: textToFormat)

let professionalProduct = ProfessionalVersionFeature()
professionalProduct.textOperation(text: textToFormat)

let ultimateProduct = UltimateVersionFeature()
ultimateProduct.textOperation(text: textToFormat)

let ultimateLightProduct = UltimateLightVersionFeature()
ultimateLightProduct.textOperation(text: textToFormat)

O exemplo pode ser desenvolvido para maior clareza, mas mesmo agora a complexidade de suportar um sistema baseado em uma base de herança é visível – complicado e falta de flexibilidade.
Um decorador é um conjunto de protocolos que descreve a funcionalidade, uma classe abstrata contendo uma referência a uma instância concreta filha da classe decoradora que estende a funcionalidade.
Vamos reescrever o exemplo acima usando o padrão:

protocol Feature {
	func textOperation(text: String)
}

class FeatureDecorator: Feature {
	private var feature: Feature?
	
	init(feature: Feature? = nil) {
		self.feature = feature
	}
	
	func textOperation(text: String) {
		feature?.textOperation(text: text)
	}
}

class BasicVersionFeature: FeatureDecorator {
	override func textOperation(text: String) {
		super.textOperation(text: text)
		print("\(text.count)")
	}
}

class ProfessionalVersionFeature: FeatureDecorator {
	override func textOperation(text: String) {
		super.textOperation(text: text)
		print("\(text.uppercased())")
	}
}

class UltimateVersionFeature: FeatureDecorator {
	override func textOperation(text: String) {
		super.textOperation(text: text)
		print("ULTIMATE: \(text)")
	}
}

let textToFormat = "Hello Decorator"

let basicProduct = BasicVersionFeature(feature: UltimateVersionFeature())
basicProduct.textOperation(text: textToFormat)

let professionalProduct = ProfessionalVersionFeature(feature: UltimateVersionFeature())
professionalProduct.textOperation(text: textToFormat)

let ultimateProduct = BasicVersionFeature(feature: UltimateVersionFeature(feature: ProfessionalVersionFeature()))
ultimateProduct.textOperation(text: textToFormat)

let ultimateLightProduct = BasicVersionFeature(feature: UltimateVersionFeature())
ultimateLightProduct.textOperation(text: textToFormat)

Agora podemos criar variações de qualquer tipo de produto – basta inicializar os tipos combinados na fase de inicialização do aplicativo; o exemplo abaixo é a criação da versão Ultimate + Professional:

ultimateProfessionalProduct.textOperation(text: textToFormat)

Fontes

https://refactoring.guru/ru/design-patterns/decorator

Código fonte

https://gitlab.com/demensdeum/patterns

Mediador de padrões

O padrão Mediador refere-se a padrões de design comportamentais.

Um dia você recebe um pedido para desenvolver um aplicativo de piadas – um aplicativo de piadas. o usuário pressiona um botão no meio da tela e ouve-se um som engraçado de um pato grasnando.
Depois de fazer o upload para a app store, o aplicativo se torna um sucesso: todo mundo grasna seu aplicativo, Elon Musk grasna em seu Instagram no próximo lançamento de um túnel de altíssima velocidade em Marte, Hillary Clinton supera Donald Trump no debate e vence as eleições na Ucrânia, sucesso!
A implementação ingênua do aplicativo é assim:

class DuckButton {
    func didPress() {
        print("quack!")
    }
}

let duckButton = DuckButton()
duckButton.didPress()

Em seguida você decide adicionar o som de um cachorro latindo, para isso você precisa mostrar dois botões para selecionar o som – com um pato e um cachorro. Vamos criar duas classes de botões: DuckButton e DogButton.
Altere o código:

class DuckButton {
    func didPress() {
        print("quack!")
    }
}

class DogButton {
    func didPress() {
        print("bark!")
    }
}

let duckButton = DuckButton()
duckButton.didPress()

let dogButton = DogButton()
dogButton.didPress()

Depois de mais um sucesso, adicionamos o som do guincho de um porco, agora existem três classes de botões:

class DuckButton {
    func didPress() {
        print("quack!")
    }
}

class DogButton {
    func didPress() {
        print("bark!")
    }
}

class PigButton {
    func didPress() {
        print("oink!")
    }
}

let duckButton = DuckButton()
duckButton.didPress()

let dogButton = DogButton()
dogButton.didPress()

let pigButton = PigButton()
pigButton.didPress()

Os usuários reclamam que os sons se sobrepõem.
Adicionamos uma verificação para evitar que isso aconteça e, ao mesmo tempo, apresentamos as classes umas às outras:

class DuckButton {
    var isMakingSound = false
    var dogButton: DogButton?
    var pigButton: PigButton?
    func didPress() {
        guard dogButton?.isMakingSound ?? false == false &&
                pigButton?.isMakingSound ?? false == false else { return }
        isMakingSound = true
        print("quack!")
        isMakingSound = false
    }
}

class DogButton {
    var isMakingSound = false
    var duckButton: DuckButton?
    var pigButton: PigButton?
    func didPress() {
        guard duckButton?.isMakingSound ?? false == false &&
                pigButton?.isMakingSound ?? false == false else { return }
        isMakingSound = true
        print("bark!")
        isMakingSound = false
    }
}

class PigButton {
    var isMakingSound = false
    var duckButton: DuckButton?
    var dogButton: DogButton?
    func didPress() {
        guard duckButton?.isMakingSound ?? false == false && 
                dogButton?.isMakingSound ?? false == false else { return }
        isMakingSound = true
        print("oink!")
        isMakingSound = false
    }
}

let duckButton = DuckButton()
duckButton.didPress()

let dogButton = DogButton()
dogButton.didPress()

let pigButton = PigButton()
pigButton.didPress()

Na esteira do sucesso de seu aplicativo, o governo decide fazer uma lei segundo a qual charlatões, latidos e grunhidos em dispositivos móveis só podem ser feitos das 9h às 15h nos demais dias de semana; Nesse momento, o usuário do seu aplicativo corre o risco de ser preso por 5 anos por produção de som obsceno usando dispositivos eletrônicos pessoais.
Altere o código:

import Foundation

extension Date {
    func mobileDeviceAllowedSoundTime() -> Bool {
        let hour = Calendar.current.component(.hour, from: self)
        let weekend = Calendar.current.isDateInWeekend(self)
        
        let result = hour >= 9 && hour <= 14 && weekend == false
        
        return result
    }
}

class DuckButton {
    var isMakingSound = false
    var dogButton: DogButton?
    var pigButton: PigButton?
    func didPress() {
        guard dogButton?.isMakingSound ?? false == false &&
                pigButton?.isMakingSound ?? false == false &&
                 Date().mobileDeviceAllowedSoundTime() == true else { return }
        isMakingSound = true
        print("quack!")
        isMakingSound = false
    }
}

class DogButton {
    var isMakingSound = false
    var duckButton: DuckButton?
    var pigButton: PigButton?
    func didPress() {
        guard duckButton?.isMakingSound ?? false == false &&
                pigButton?.isMakingSound ?? false == false &&
                 Date().mobileDeviceAllowedSoundTime() == true else { return }
        isMakingSound = true
        print("bark!")
        isMakingSound = false
    }
}

class PigButton {
    var isMakingSound = false
    var duckButton: DuckButton?
    var dogButton: DogButton?
    func didPress() {
        guard duckButton?.isMakingSound ?? false == false && 
                dogButton?.isMakingSound ?? false == false &&
                 Date().mobileDeviceAllowedSoundTime() == true else { return }
        isMakingSound = true
        print("oink!")
        isMakingSound = false
    }
}

let duckButton = DuckButton()
let dogButton = DogButton()
let pigButton = PigButton()

duckButton.dogButton = dogButton
duckButton.pigButton = pigButton

dogButton.duckButton = duckButton
dogButton.pigButton = pigButton

pigButton.duckButton = duckButton
pigButton.dogButton = dogButton

duckButton.didPress()
dogButton.didPress()
pigButton.didPress()

De repente, o aplicativo lanterna começa a tirar o nosso do mercado, não vamos deixar que ele nos derrote e adicione uma lanterna pressionando o botão “oink-oink” e o restante dos botões:

import Foundation

extension Date {
    func mobileDeviceAllowedSoundTime() -> Bool {
        let hour = Calendar.current.component(.hour, from: self)
        let weekend = Calendar.current.isDateInWeekend(self)
        
        let result = hour >= 9 && hour <= 14 && weekend == false
        
        return result
    }
}

class Flashlight {

    var isOn = false

    func turn(on: Bool) {
        isOn = on
    }
}

class DuckButton {
    var isMakingSound = false
    var dogButton: DogButton?
    var pigButton: PigButton?
    var flashlight: Flashlight?
    func didPress() {
        flashlight?.turn(on: true)
        guard dogButton?.isMakingSound ?? false == false &&
                pigButton?.isMakingSound ?? false == false &&
                 Date().mobileDeviceAllowedSoundTime() == true else { return }
        isMakingSound = true
        print("quack!")
        isMakingSound = false
    }
}

class DogButton {
    var isMakingSound = false
    var duckButton: DuckButton?
    var pigButton: PigButton?
    var flashlight: Flashlight?
    func didPress() {
        flashlight?.turn(on: true)
        guard duckButton?.isMakingSound ?? false == false &&
                pigButton?.isMakingSound ?? false == false &&
                 Date().mobileDeviceAllowedSoundTime() == true else { return }
        isMakingSound = true
        print("bark!")
        isMakingSound = false
    }
}

class PigButton {
    var isMakingSound = false
    var duckButton: DuckButton?
    var dogButton: DogButton?
    var flashlight: Flashlight?
    func didPress() {
        flashlight?.turn(on: true)
        guard duckButton?.isMakingSound ?? false == false && 
                dogButton?.isMakingSound ?? false == false &&
                 Date().mobileDeviceAllowedSoundTime() == true else { return }
        isMakingSound = true
        print("oink!")
        isMakingSound = false
    }
}

let flashlight = Flashlight()
let duckButton = DuckButton()
let dogButton = DogButton()
let pigButton = PigButton()

duckButton.dogButton = dogButton
duckButton.pigButton = pigButton
duckButton.flashlight = flashlight

dogButton.duckButton = duckButton
dogButton.pigButton = pigButton
dogButton.flashlight = flashlight

pigButton.duckButton = duckButton
pigButton.dogButton = dogButton
pigButton.flashlight = flashlight

duckButton.didPress()
dogButton.didPress()
pigButton.didPress()

Como resultado, temos um aplicativo enorme que contém muito código de copiar e colar, as classes internas são conectadas entre si por um link morto - não há acoplamento fraco, tal milagre é muito difícil de manter e mudança no futuro devido às grandes chances de cometer um erro.

Use o Mediador

Vamos adicionar uma classe mediadora intermediária - ApplicationController. Esta classe fornecerá baixo acoplamento de objetos, garantirá a separação de responsabilidades entre classes e eliminará código duplicado.
Vamos reescrever:

import Foundation

class ApplicationController {

    private var isMakingSound = false
    private let flashlight = Flashlight()
    private var soundButtons: [SoundButton] = []

    func add(soundButton: SoundButton) {
        soundButtons.append(soundButton)
    }
    
    func didPress(soundButton: SoundButton) {
        flashlight.turn(on: true)
        guard Date().mobileDeviceAllowedSoundTime() && 
                isMakingSound == false else { return }
        isMakingSound = true
        soundButton.didPress()
        isMakingSound = false
    }
}

class SoundButton {
    let soundText: String
    
    init(soundText: String) {
        self.soundText = soundText
    }
    
    func didPress() {
        print(soundText)
    }
}

class Flashlight {
    var isOn = false

    func turn(on: Bool) {
        isOn = on
    }
}

extension Date {
    func mobileDeviceAllowedSoundTime() -> Bool {
        let hour = Calendar.current.component(.hour, from: self)
        let weekend = Calendar.current.isDateInWeekend(self)
        
        let result = hour >= 9 && hour <= 14 && weekend == false
        
        return result
    }
}

let applicationController = ApplicationController()
let pigButton = SoundButton(soundText: "oink!")
let dogButton = SoundButton(soundText: "bark!")
let duckButton = SoundButton(soundText: "quack!")

applicationController.add(soundButton: pigButton)
applicationController.add(soundButton: dogButton)
applicationController.add(soundButton: duckButton)

pigButton.didPress()
dogButton.didPress()
duckButton.didPress()

Muitos artigos sobre arquiteturas de aplicativos de interface de usuário descrevem o padrão MVC e seus derivados. O modelo é utilizado para trabalhar com dados lógicos de negócio, a visualização ou representação mostra informações ao usuário na interface/fornece interação com o usuário, o controlador é um mediador que garante a interação dos componentes do sistema.

Fontes

https://refactoring.guru/ru/design-patterns/ mediador

Código fonte

https://gitlab.com/demensdeum/patterns/< /p>

Strategy pattern

The Strategy pattern allows you to select the type of algorithm that implements a common interface, right while the application is running. This pattern refers to the behavioral design patterns.

Sun Tzu

Suppose we are developing a music player with embedded codecs. The built-in codecs imply reading music formats without using external sources of the operating system (codecs), the player should be able to read tracks of different formats and play them. VLC player has such capabilities, it supports various types of video and audio formats, it runs on popular and not very operating systems.

Imagine what a naive player implementation looks like:

var player: MusicPlayer?

func play(filePath: String) {
    let extension = filePath.pathExtension

    if extension == "mp3" {
        playMp3(filePath)
    }
    else if extension == "ogg" {
        playOgg(filePath)
    }
}

func playMp3(_ filePath: String) {
    player = MpegPlayer()
    player?.playMp3(filePath)
}

func playOgg(_ filePath: String) {
    player = VorbisPlayer()
    player?.playMusic(filePath)
}

Next, we add several formats, which leads to the need to write additional methods. Plus, the player must support plug-in libraries, with new audio formats that will appear later. There is a need to switch the music playback algorithm, the Strategy pattern is used to solve this problem.

Let’s create a common protocol MusicPlayerCodecAlgorithm, write the implementation of the protocol in two classes MpegMusicPlayerCodecAlgorithm and VorbisMusicPlayerCodecAlgorithm, to play mp3 and ogg files with-but. Create a class MusicPlayer, which will contain a reference for the algorithm that needs to be switched, then by the file extension we implement codec type switching:

import Foundation

class MusicPlayer {
    var playerCodecAlgorithm: MusicPlayerCodecAlgorithm?
    
	func play(_ filePath: String) {
            playerCodecAlgorithm?.play(filePath)
	}
}

protocol MusicPlayerCodecAlgorithm {
    func play(_ filePath: String)
}

class MpegMusicPlayerCodecAlgorithm: MusicPlayerCodecAlgorithm {
	func play(_ filePath: String) {
		debugPrint("mpeg codec - play")
	}
}

class VorbisMusicPlayerCodecAlgorithm: MusicPlayerCodecAlgorithm {
	func play(_ filePath: String) {
		debugPrint("vorbis codec - play")	
	}
}

func play(fileAtPath path: String) {
	guard let url = URL(string: path) else { return }
	let fileExtension = url.pathExtension
		
	let musicPlayer = MusicPlayer()
	var playerCodecAlgorithm: MusicPlayerCodecAlgorithm? 
		
	if fileExtension == "mp3" {
                playerCodecAlgorithm = MpegMusicPlayerCodecAlgorithm()
	}
	else if fileExtension == "ogg" {
                playerCodecAlgorithm = VorbisMusicPlayerCodecAlgorithm()
	}
		
	musicPlayer.playerCodecAlgorithm = playerCodecAlgorithm
	musicPlayer.playerCodecAlgorithm?.play(path)
}

play(fileAtPath: "Djentuggah.mp3")
play(fileAtPath: "Procrastinallica.ogg")

The above example also shows the simplest example of a factory (switching the codec type from the file extension) It is important to note that the Strategy strategy does not create objects, it only describes how to create a common interface for switching the family of algorithms.

Documentation

https://refactoring.guru/en/design-patterns/strategy

Source code

https://gitlab.com/demensdeum/patterns/

Iterator pattern

In this article I will describe the Iterator pattern.
This pattern refers to the behavioral design patterns.

Print it

Suppose we need to print a list of tracks from the album “Procrastinate them all” of the group “Procrastinallica”.
The naive implementation (Swift) looks like this:

for i=0; i < tracks.count; i++ {
    print(tracks[i].title)
}

Suddenly during compilation, it is detected that the class of the tracks object does not give the number of tracks in the count call, and moreover, its elements cannot be accessed by index. Oh…

Filter it

Suppose we are writing an article for the magazine “Wacky Hammer”, we need a list of tracks of the group “Djentuggah” in which bpm exceeds 140 beats per minute. An interesting feature of this group is that its records are stored in a huge collection of underground groups, not sorted by albums, or for any other grounds. Let’s imagine that we work with a language without functionality:

var djentuggahFastTracks = [Track]()

for track in undergroundCollectionTracks {
    if track.band.title == "Djentuggah" && track.info.bpm == 140 {
        djentuggahFastTracks.append(track)
    }
}

Suddenly, a couple of tracks of the group are found in the collection of digitized tapes, and the editor of the magazine suggests finding tracks in this collection and writing about them. A Data Scientist friend suggests to use the Djentuggah track classification algorithm, so you don’t need to listen to a collection of 200 thousand tapes manually. Try:

var djentuggahFastTracks = [Track]()

for track in undergroundCollectionTracks {
    if track.band.title == "Djentuggah" && track.info.bpm == 140 {
        djentuggahFastTracks.append(track)
    }
}

let tracksClassifier = TracksClassifier()
let bpmClassifier = BPMClassifier()

for track in cassetsTracks {
    if tracksClassifier.classify(track).band.title == "Djentuggah" && bpmClassifier.classify(track).bpm == 140 {
        djentuggahFastTracks.append(track)
    }
}

Mistakes

Now, just before sending to print, the editor reports that 140 beats per minute are out of fashion, people are more interested in 160, so the article should be rewritten by adding the necessary tracks.
Apply changes:

var djentuggahFastTracks = [Track]()

for track in undergroundCollectionTracks {
    if track.band.title == "Djentuggah" && track.info.bpm == 160 {
        djentuggahFastTracks.append(track)
    }
}

let tracksClassifier = TracksClassifier()
let bpmClassifier = BPMClassifier()

for track in cassetsTracks {
    if tracksClassifier.classify(track).band.title == "Djentuggah" && bpmClassifier.classify(track).bpm == 140 {
        djentuggahFastTracks.append(track)
    }
}

The most attentive ones noticed an error; the bpm parameter was changed only for the first pass through the list. If there were more passes through the collections, then the chance of a mistake would be higher, that is why the DRY principle should be used. The above example can be developed further, for example, by adding the condition that you need to find several groups with different bpm, by the names of vocalists, guitarists, this will increase the chance of error due to duplication of code.

Behold the Iterator!

In the literature, an iterator is described as a combination of two protocols / interfaces, the first is an iterator interface consisting of two methods – next(), hasNext(), next() returns an object from the collection, and hasNext() reports that there is an object and the list is not over. However in practice, I observed iterators with one method – next(), when the list ended, null was returned from this object. The second is a collection that should have an interface that provides an iterator – the iterator() method, there are variations with the collection interface that returns an iterator in the initial position and in end – the begin() and end() methods are used in C ++ std.
Using the iterator in the example above will remove duplicate code, eliminate the chance of mistake due to duplicate filtering conditions. It will also be easier to work with the collection of tracks on a single interface – if you change the internal structure of the collection, the interface will remain old and the external code will not be affected.
Wow!

let bandFilter = Filter(key: "band", value: "Djentuggah")
let bpmFilter = Filter(key: "bpm", value: 140)
let iterator = tracksCollection.filterableIterator(filters: [bandFilter, bpmFilter])

while let track = iterator.next() {
    print("\(track.band) - \(track.title)")
}

Changes

While the iterator is running, the collection may change, thus causing the iterator’s internal counter to be invalid, and generally breaking such a thing as “next object”. Many frameworks contain a check for changing the state of the collection, and in case of changes they return an error / exception. Some implementations allow you to remove objects from the collection while the iterator is running, by providing the remove() method in the iterator.

Documentation

https://refactoring.guru/en/design-patterns/iterator

Source code

https://gitlab.com/demensdeum/patterns/

Padrão “Instantâneo”

Neste post irei descrever o padrão “Snapshot” ou “Memento”

Este padrão refere-se a padrões “Comportamentais” padrões de design.

Suponha que estamos desenvolvendo um editor gráfico e precisamos adicionar a capacidade de reverter ações mediante um comando do usuário. Também é muito importante que os componentes do sistema não tenham acesso ao estado interno das “ações” seu estado interno, fornecendo uma interface externa clara e simples. Para resolver este problema, o padrão “Snapshot” ou “Guardião”.

Exemplo de trabalho “Instantâneo” apresentado abaixo:


Ao clicar, um sprite aparece, ao clicar na seta enrolada, a ação é cancelada – O sprite desaparece. O exemplo consiste em três classes:

  1. Tela na qual os sprites e a interface gráfica são exibidos.
  2. Controlador de tela, processa cliques e controla a lógica da tela.
  3. Os estados da tela que persistem a cada alteração são revertidos, se necessário, usando o controlador de tela.

No contexto do padrão “Snapshot” as aulas são:

  1. Tela – fonte, os estados desta classe são salvos como “instantâneos”, para reversão posterior mediante solicitação. Além disso, a fonte deve ser capaz de restaurar o estado ao transferir um “instantâneo”
  2. Controlador – custodiante, esta classe sabe como e quando salvar/reverter estados.
  3. Estado – snapshot, uma classe que armazena o estado da origem, além de informações de data ou um índice a partir do qual a ordem de reversão pode ser estabelecida com precisão.

Uma característica importante do padrão é que apenas a fonte deve ter acesso aos campos internos do estado salvo no instantâneo. Isso é necessário para proteger os instantâneos de alterações externas (de desenvolvedores úteis que desejam alterar algo ignorando o encapsulamento; , quebrando a lógica do sistema). Para implementar o encapsulamento, são usadas classes integradas e, em C++, elas usam a capacidade de especificar classes amigas. Pessoalmente, implementei uma versão simples sem encapsulamento para Rise e usando Generic ao implementar para Swift. Na minha versão – Memento fornece seu estado interno apenas para entidades do mesmo estado de classe:

Fontes

https://refactoring.guru/design-patterns/memento

Código fonte

https://gitlab.com/demensdeum/patterns/< /p>

Padrão de visitante

Neste post irei descrever um padrão de design chamado “Visitante” ou “Visitante”
Este padrão pertence ao grupo de Padrões de comportamento.

Vamos encontrar um problema

Esse padrão é usado principalmente para contornar a limitação do envio único em linguagens de ligação antecipada.

Alice X por NFGPhoto (CC-2.0)
Vamos criar uma classe/protocolo abstrato Band, fazer uma subclasse de MurpleDeep, criar uma classe Visitor com dois métodos – um para enviar qualquer descendente de Band para o console, o segundo para gerar qualquer MurpleDeep, o principal é que os nomes (assinaturas) dos métodos sejam os mesmos e os argumentos diferem apenas por classe. Usando o método de impressão intermediário com o argumento Band, criamos uma instância de Visitor e chamamos o método visit para MurpleDeep.
Abaixo está o código em Kotlin:

A saída será “Esta é a classe Band

Como isso é possível?!

Por que isso acontece é descrito em palavras inteligentes em muitos artigos, inclusive em russo, mas sugiro que você imagine como o compilador vê o código, talvez tudo fique claro imediatamente:

Resolvendo o problema

Existem muitas soluções para resolver este problema, a seguir consideraremos uma solução usando o padrão Visitor.
Adicionamos o método Accept com o argumento Visitor à classe/protocolo abstrato, chamamos Visitor.visit(this) dentro do método e, em seguida, adicionamos uma substituição/implementação do método Accept à classe MurpleDeep, violando DRY de forma decisiva e calma, novamente escrevendo visitante.visita(este).< br />Código final:

Fontes

https://refactoring.guru/ru/ padrões de design/visitor-double-dispatch

Código fonte

https://gitlab.com/demensdeum/patterns

Padrão peso mosca

Neste post irei descrever o padrão estrutural “Leve” ou “Oportunista” (Peso mosca)
Este padrão pertence ao grupo de Padrões Estruturais.

Vejamos abaixo um exemplo de como o padrão funciona:


Por que é necessário? Para economizar RAM. Concordo que em tempos de uso generalizado do Java (que consome CPU e memória à toa), isso não é mais tão importante, mas vale a pena usar.
No exemplo acima, apenas 40 objetos são gerados, mas se você aumentar o número para 120.000, o consumo de memória aumentará proporcionalmente.
Vejamos o consumo de memória sem usar o padrão flyweight no navegador Chromium:

Sem usar um padrão, o consumo de memória é de aproximadamente 300 megabytes.

Agora vamos adicionar um padrão ao aplicativo e ver o consumo de memória:

Usando o padrão, o consumo de memória é de aproximadamente 200 megabytes, então economizamos 100 megabytes de memória no aplicativo de teste; em projetos sérios, a diferença pode ser muito maior.

Como funciona?

No exemplo acima, desenhamos 40 gatos, ou para maior clareza, 120 mil. Cada gato é carregado na memória como uma imagem png e, na maioria das renderizações, ele é convertido em um bitmap para renderização (na verdade, bmp), isso é feito para aumentar a velocidade, já que um png compactado leva muito tempo para ser renderizado. Sem usar o padrão, carregamos 120 mil fotos de gatos na RAM e desenhamos, mas ao usar o padrão “leve” carregamos um gato na memória e desenhamos 120 mil vezes com diferentes posições e transparência. A mágica é que implementamos coordenadas e transparência separadamente da imagem do gato durante a renderização, a renderização pega apenas um gato e usa um objeto com coordenadas e transparência para a renderização correta.

Como é isso no código?

A seguir estão exemplos da linguagem Rise

Sem usar um padrão:


A imagem do gato é carregada separadamente para cada objeto no loop – catImage.

Usando padrão:

Uma imagem de um gato é usada por 120 mil objetos.

Onde é usado?

Usado em estruturas GUI, por exemplo, o “reuse” (reutilizar) células da tabela UITableViewCell, o que aumenta a barreira de entrada para iniciantes que não conhecem esse padrão. Também comumente usado no desenvolvimento de jogos.

Código fonte

https://gitlab.com/demensdeum/patterns/< /p>

Fontes

https://refactoring.guru/ru/design-patterns/ peso mosca
http://gameprogrammingpatterns.com/flyweight.html

O bom, o mau e o feio Singleton

Nesta nota descreverei minha experiência e a experiência de meus colegas ao trabalhar com o padrão Singleton (Singleton na literatura estrangeira), enquanto trabalhava em vários projetos (bem-sucedidos e não tão bem-sucedidos). Descreverei por que pessoalmente acho que esse padrão não pode ser usado em lugar nenhum e também descreverei quais fatores psicológicos na equipe influenciam a integração desse antipadrão. Dedicado a todos os desenvolvedores caídos e aleijados que estavam tentando entender por que tudo começou com um dos membros da equipe trazendo um cachorrinho fofo, fácil de manusear, que não requer cuidados e conhecimentos especiais para cuidar dele, e terminou com a fera criada tomar seu projeto como refém, requer cada vez mais horas de trabalho e consome os nervos do usuário, seu dinheiro e cria números absolutamente monstruosos para avaliar a implementação de coisas aparentemente simples coisas.


Wolf in sheep’s clothing by SarahRichterArt

A história se passa em um universo alternativo, todas as coincidências são aleatórias…

Acaricie o gato em casa com Cat@Home

Toda pessoa às vezes na vida tem um desejo irresistível de acariciar um gato. Analistas de todo o mundo prevêem que a primeira startup que criou um aplicativo para entrega e aluguel de gatos se tornará extremamente popular e em um futuro próximo será comprada pelo Moogle por trilhões de dólares. Logo isso acontece – um cara de Tyumen cria o aplicativo Cat@Home e logo se torna um trilionário, a empresa Moogle obtém uma nova fonte de lucro e milhões de pessoas estressadas têm a oportunidade de peça um gato para sua casa para passar mais roupas e se acalmar.

Ataque dos Clones

Um dentista extremamente rico de Murmansk, Alexey Goloborodko, impressionado com um artigo sobre Cat@Home da Forbes, decide que também quer ser astronomicamente rico. Para atingir esse objetivo, através de seus amigos, ele encontra uma empresa de Goldfield – que é a cidade de Goldfield. Wakeboard DevPops, que fornece serviços de desenvolvimento de software, encomenda a eles o desenvolvimento de um clone Cat@Home.

Time vencedor

O projeto se chama Fur&Pure, confiado a uma talentosa equipe de desenvolvimento de 20 pessoas; A seguir, vamos nos concentrar em uma equipe de desenvolvimento móvel de 5 pessoas. Cada membro da equipe recebe sua parte do trabalho, munido de ágil e scrum, a equipe conclui o desenvolvimento no prazo (em seis meses), sem bugs, lança o aplicativo na iStore, onde é avaliado em 5 por 100.000 usuários, há muitos comentários sobre o quão bom é o aplicativo, quão excelente é o serviço (afinal, universo alternativo). Os gatos estão passados, o aplicativo está liberado, tudo parece estar indo bem. Porém, Moogle não tem pressa em comprar uma startup por trilhões de dólares, porque não só gatos, mas também cachorros já apareceram no Cat@Home.

O cachorro late, a caravana segue em frente

O proprietário do aplicativo decide que é hora de adicionar cães ao aplicativo, solicita uma avaliação à empresa e recebe aproximadamente pelo menos seis meses para adicionar cães ao aplicativo. Na verdade, o aplicativo será escrito do zero novamente. Durante esse período, o Moogle adicionará cobras, aranhas e porquinhos-da-índia ao aplicativo, e o Fur&Pur receberá apenas cães.
Por que isso aconteceu? A falta de arquitetura de aplicação flexível é a culpada por tudo; um dos fatores mais comuns é o antipadrão Singleton.

O que há de errado?

Para encomendar um gato em casa o consumidor precisa criar um pedido e encaminhá-lo para o escritório, onde o escritório irá processá-lo e enviar um entregador com o gato, o entregador já receberá o pagamento pelo serviço.
Um dos programadores decide criar uma classe “Cat Application” com os campos necessários, traz esta classe para o espaço global da aplicação através de um singleton. Por que ele está fazendo isso? Para economizar tempo (economizando meia hora), porque é mais fácil tornar um aplicativo público do que pensar na arquitetura do aplicativo e usar injeção de dependência. Então outros desenvolvedores pegam esse objeto global e vinculam suas classes a ele.
Por exemplo, todas as telas acessam o objeto global “Cat Request” e mostrar dados do aplicativo. Como resultado, esse aplicativo monolítico é testado e lançado.
Tudo parece estar bem, mas de repente aparece um cliente com a necessidade de adicionar solicitações de cães ao aplicativo. A equipe começa a avaliar freneticamente quantos componentes do sistema serão afetados por essa mudança. Ao final da análise, verifica-se que é necessário refazer de 60 a 90% do código para ensinar a aplicação a aceitar não apenas “Request For Cat” mas também “Pedido de Cachorro”, já é inútil avaliar a adição de outros animais nesta fase, para dar conta de pelo menos dois.

Como prevenir o singleton

Primeiro, na fase de levantamento de requisitos, indique explicitamente a necessidade de criar uma arquitetura flexível e extensível. Em segundo lugar, vale a pena realizar uma revisão independente do código do produto paralelamente, com pesquisa obrigatória dos pontos fracos. Se você é um desenvolvedor e adora singletons, sugiro que recupere o juízo antes que seja tarde demais, caso contrário, noites sem dormir e nervos em frangalhos serão garantidos. Se você estiver trabalhando em um projeto legado que possui muitos singletons, tente se livrar deles ou do projeto o mais rápido possível.
Você precisa mudar do antipadrão de objetos/variáveis ​​globais singletons para injeção de dependência – o padrão de design mais simples no qual todos os dados necessários são fornecidos a uma instância de uma classe no estágio de inicialização, sem necessidade adicional de serem vinculados ao espaço global.

Fontes

https://stackoverflow. com/questions/137975/what-is-so-bad-about-singletons
http://misko.hevery.com/2008/08/17/singletons-are-pathological-liars/
https://blog.ndepend.com/singleton-pattern-costs/

Relatório de desenvolvimento da Máscara da Morte 1

Nova seção não permanente “diários do desenvolvedor” ou Diário do Desenvolvedor em estilo estrangeiro.
O desenvolvimento do jogo Death-Mask está a todo vapor, o logotipo do motor foi adicionado para os jogos Flame Steel Engine 2019, tela para seleção do mapa inicial por ilha (verde, vermelho, preto, branco), saída de texturas para paredes, teto, piso do labirinto, aumento do tamanho da área de jogo.


Mapa da Cidade da Zona Vermelha

Em seguida, planejamos adicionar modelos 3D para o ambiente, em vez de sprites estilo Doom, e planejamos adicionar modelos para armas, caixas, inimigos e amigos. Na jogabilidade está prevista a adição de moeda, lojas, a possibilidade de comprar partes do mapa do jogo indicando locais interessantes com saques e a possível localização da “Máscara da Morte”. Também quero adicionar a possibilidade de contratar companheiros para viajar pelo labirinto cibernético.
Acompanhe as novidades.

Rápido 4.2.3 – Ubuntu 18.10

Construindo o Swift com as bibliotecas necessárias para rodar no Ubuntu 18.10. Versão mais recente disponível no site da Apple – para Ubuntu 18.04. Baseado na montagem do site oficial com adição de bibliotecas do Ubuntu 18.04. Também foi adicionado um script de exemplo para adicionar PATH e LD_LIBRARY_PATH ao terminal bash:
http://www.mediafire.com/file/lrs74mvoj3fti03/swift4.2.3.ubuntu.18.10.x86_64.tar.gz/file

Linguagem declarativa Zakaz

Apresento a sua atenção uma linguagem de programação declarativa pura – Zakaz. A ideia principal da nova linguagem – a aplicação contém comandos de execução escritos em formato livre que devem ser executados pelos “executores”. Se nenhum “artista” não puder executar o comando, a execução do programa será interrompida. As aplicações são chamadas de especificações técnicas (tez) e devem ter extensão .tez. A sintaxe Zakaz requer duas regras:

  • Cada comando começa em uma nova linha
  • Cada comando deve ser escrito em linguagem formal que seja compreensível para os humanos

Exemplo Olá Mundo.tez:

Mostrar texto "Hello World" na telaMostrar "Exemplo de Zakaz 'tez'" na tela

Um exemplo de especificação que exibe uma descrição do princípio de funcionamento e abertura do site http://demensdeum.com no navegador Firefox

Mostrar texto "Mostrar demonstração do site" na telaShow "Você precisa do Firefox instalado em seu sistema para executar este 'tez', e ele deve ser chamado através de \"sistema\" Função C" texto na telaMostrar "Também deve haver \"FirefoxPerformer\" atribuído ao Zakaz Runtime, verifique o manual para mais informações" texto na telaMostrar site com endereço "http://demensdeum.com" no Firefox

Você deve executar o exemplo acima junto com o “executor” FirefoxPerformer, que é capaz de processar o comando mais recente para renderizar um site através do Firefox

./ZakazRuntime openDemensdeumSite.tez FirefoxPerformer

Para implementar seu executor, você precisa implementá-lo como uma biblioteca dinâmica usando a classe abstrata ZakazRuntime::Performer e retorná-lo junto com um ponteiro inteligente do método de função global createPerformer(). Você pode usar a implementação do FirefoxPerformer como exemplo.

Código fonte

https://gitlab.com/demensdeum/zakaz