Existe algum conceito de “orquestrador” em linguagens de programação?

Imagine que eu estou numa linha de montagem de automóveis. Para simplificar imaginemos apenas a carroceria, rodas e motor.

Agora imagine( está quase lá) que só haja máquinas e você tenha que construir um carro. Cada máquina não interage com a outra, de forma direta, elas fazem o trabalho que lhes é mandado.

Então podemos fazer assim:

  1. A maquina pega a corroceria e coloca no trilho.

  2. Chega na parte do motor e a máquina coloca o motor na carroceria

Imagine agora que essas “máquinas”são classes.

Daria para fazer o 1) e 2) chamando em sequencia Class1 e Class 2. Até aí tudo bem.

Vamos para a parte das rodas.

  1. A máquina de colocar rodas recebe uma caixa com 4 rodas e coloca no eixo.

  2. A máquina de parafusar, parafusa os 4 parafusos da roda

Suponha agora que, por motivos quaisquer, cada roda tem que ser totalmente montada uma de cada vez.

Imagine que 3 e 4 são classes: Class3 e Class4.

Se eu chamar sequencialmente Class3 e depois Class4, vai dar problema pois Class4 vai colocar todas as rodas e Class 3 vai parafusar depois tudo.

Qual o conceito, ferramenta( não sei como se chama) de dizer assim:

“Class3, coloca apenas uma roda e espera. Class4, parafusa os parafusos todos dessa roda. Class3, coloca outra roda… E assim até terminar”

Não sei se ficou claro…

Obrigado.

1 curtida

:wave:t2:

Eu acredito que a função sleep do bash poderia servir como uma orquestradora, digamos assim.
No caso ela serve para “esperar um tempo para depois executar comandos e/ou instruções”.

Um exemplo, irei screenshot a tela, eu uso o comando grim. Mas acontece que ele é instantâneo, digitou apertou enter e screenshot, mas e se eu quero screenshot outra de tela?

Ae entra em ação o comando sleep:

(sleep 5 ; grim)

Oque ele irá fazer ae é chamar a função sleep, aguardar 5 segundos para executar o comando grim. Dando tempo de eu me preparar para screenshot outra tela. Eu também posso alterar aquele 5 para qualquer outro número, determinando os segundos que o sleep terá de esperar e ainda mais. Eu acho que não se limita somente a números, podendo se colocar variáveis que estão no outro script, algoritmo e tals, como se fosse um DOM Manipulation.

Então se o Bash tem isso, eu penso que todas as outras linguagens de programação também tem este orquestrador, afinal de contas, tudo são pulsos elétricos correndo dentro dos hardware até seus destinos em altíssima quantidade e velocidade, conhecidos como os famosos binários 0 e 1.

:vulcan_salute:t2:

O termo é o mesmo usado em geral na computação, scheduler ou scheduling e é um assunto base em Computação paralela, pode ser tanto via threads como via processos, pode envolver uma queue (fila), ordenada ou não, pode ser síncrono ou assíncrono envolver paralelismo e ou concorrência, etc. As linguagens no geral incluem bibliotecas ou ferramentas para controlar isso tais como primitivas sync/async/timeout/sleep. Em OOP é comum você encontrar abstrações como classes Queue, OrderedQueue, Process, Scheduler, Thread, etc.

3 curtidas

Em prototipagem meio que isso é o worflow natural, em OOP com classes, acho que ficaria assim:

  1. Você poderia fazer assim, primeiro declarando cada classe:
// Classe 1
class ColocadorDeMotor
{
    public:
        ColocadorDeMotor();
        virtual ~ColocadorDeMotor();
    private:
        void colocaMotor(Motor*motor, Trilho*trilho);
};

// Classe 2
class ColocadorDeCarroceria
{
    public:
        ColocadorDeCarroceria();
        virtual ~ColocadorDeCarroceria();
    private:
        void colocaCarroceria(Carroceria*carroceria, Trilho*trilho);
};

// Classe 3
class ColocadorDeRodas
{
    public:
        ColocadorDeRodas();
        virtual ~ColocadorDeRodas();
    private:
        // Vou colocar assim pra evitar loops
        void colocaRoda(Roda*roda1, Roda*roda2, 
                        Roda*roda3, Roda*roda4,
                        Trilho*trilho);
};

// Classe 4
class ParafusadeiraDeRodas
{
    public:
        Parafusadeira();
        virtual ~Parafusadeira();
    private:
        void parafusaRoda(Roda*roda, Trilho*trilho);
};

  1. A implementação de cada classe, penso eu ficaria assim:
#include "Classes.h"
#include <iostream>

ColocadorDeMotor::ColocadorDeMotor()
{
    // inicializador
}

ColocadorDeMotor::~ColocadorDeMotor()
{
   // destrutor
}

void ColocadorDeMotor::colocaMotor(Motor*motor, Trilho*trilho)
{
   std::cout << "..Adicionado motor";
   trilho->setMotor(motor);
}

#include "Classes.h"
#include <iostream>

ColocadorDeCarroceria::ColocadorDeCarroceria()
{
    // inicializador
}

ColocadorDeCarroceria::~ColocadorDeCarroceria()
{
   // destrutor
}

void ColocadorDeCarroceria::colocaCarroceria(Carroceria*carroceria, Trilho*trilho)
{
   std::cout << "..Adicionado carroceria";
   trilho->setCarroceria(motor);
}

#include "Classes.h"
#include <iostream>

ColocadorDeRodas::ColocadorDeRodas()
{
    // inicializador
}

ColocadorDeRodas::~ColocadorDeRodas()
{
   // destrutor
}

void ColocadorDeRodas::colocaRoda(Roda*roda1, Roda*roda2, Roda*roda3, Roda*roda4, Trilho*trilho)
{
   std::cout << "..Adicionando rodas";

   Parafusadeira*parafusadeira = new Parafusadeira();
   std::cout << "....Adicionando roda1";
   trilho->addRoda(roda1);
   parafusadeira->parafusaRoda(roda1);
   std::cout << "....Adicionando roda2";
   trilho->addRoda(roda2);
   parafusadeira->parafusaRoda(roda2);
   std::cout << "....Adicionando roda3";
   trilho->addRoda(roda3);
   parafusadeira->parafusaRoda(roda3);
   std::cout << "....Adicionando roda4";
   trilho->addRoda(roda4);
   parafusadeira->parafusaRoda(roda4);

   delete parafusadeira;
}

#include "Classes.h"
#include <iostream>

ParafusadeiraDeRodas::ParafusadeiraDeRodas()
{
    // inicializador
}

ParafusadeiraDeRodas::~ParafusadeiraDeRodas()
{
   // destrutor
}

void ParafusadeiraDeRodas::parafusaRoda(Roda*roda, Trilho*trilho)
{
   std::cout << "......Parafusando roda";
   trilho->addRoda(roda);
}

  1. Penso eu que o orquestrador pode ser qualquer função procedural como o main:
#include "Classes.h"

int main(int argc, char *argv[])
{
    // Pega cada peça:
    Motor*motor           = Pecas::getMotor();
    Trilho*trilho         = Pecas::getTrilho();
    Carcaca*trilho        = Pecas::getCarcaca();
    Carroceria*carroceria = Pecas::getCarroceria();

    // Não vejo necessidade instanciar cada roda

    ColocadorDeMotor* colocador_de_motor = new ColocadorDeMotor();
    ColocadorDeCarroceria* colocador_de_carroceria = new ColocadorDeCarroceria();
    ColocadorDeRodas* colocador_de_rodas = new ColocadorDeRodas();

    // Note que não faz sentido instanciar a Parafusadeira, ela só
    // é realmente necessária dentro da classe ColocadorDeRodas

    colocador_de_motor->colocaMotor(motor, trilho);

    colocador_de_rodas->colocaRodas(Pecas::getRoda(),
                                    Pecas::getRoda(),
                                    Pecas::getRoda(),
                                    Pecas::getRoda());

    colocador_de_carroceria->colocaCarroceria(Carroceria*carroceria, Trilho*trilho);
}

A saída ficaria:

..Colocado Motor
..Colocando rodas
....Colocando roda1
......Parafusando roda
....Colocando roda2
......Parafusando roda
....Colocando roda3
......Parafusando roda
....Colocando roda4
......Parafusando roda
..Colocando carroceria

Note que de um jeito OOP com classes, a melhor maneira é simplesmente não chamar class4 fora de class3, basta chamar dentro e deletar depois

2 curtidas

Muito instrutivas as respostas mas eu acho que tem um problema mais sério com as minhas classes.

Enxuguei coisas desnecessárias apenas para mostrar o problema mais evidente.

OBS: Estou começando a aprender a programar algo com OOP então me corrijam se estiver usando a nomenclatura errada.

O problema é que quando chamo MyClass, vai ter um loop dentro que fará umas coisas e retornará um array 1D.

O problema é que crio duas instâncias da classe MyClass.

Os objetos são mult1.method1() e mult2.method(2).

Então o que ocorre, em ordem cronológica é:

  1. data1 = [a1,a2,…,an]

  2. data 2 = [b1,b2,…,bn]

O que deveria acontecer é:

Primeiro ser lido (a1, b1),(a2,b2), …(an,bn)

O que eu pensei em fazer ( não sei se seria uma solução “menos elegante”) era retirar o loop, deixando apenas o " do anything" e iterando no main().

Mas estava buscando algo diferente pois pensei que no caso de programas com diversas classes, o main() iria terminar virando um procedural comum.

você pode zipar data1 e data2:

> list(zip(['a1', 'a2'],['b1', 'b2']))
# [('a1', 'b1'), ('a2', 'b2')]
1 curtida

Mas não serve pois não é apenas uma construção dos dados, uma organização. Isso é uma coisa que vai controlar hardware tipo Arduino. E as coisas tem que que serem sequenciais senão perderá o sentido.

É algo do tipo: ligue a luz e tire uma foto e iterar n vezes. Se eu mandar ligar n vezes a luz e depois tirar n fotos, não adianta.

Eu acho que estou em algum dos estados:

  1. Querendo sofisticar demais para um iniciante

  2. Querendo complicar demais devido a ser iniciante.

Você vai ter de ir um pouco mais fundo, acredito que não vai bastar reorganizar a criação dos itens na origem, vai ter de adicionar pontos de espera e talvez até criar observadores, escutadores, funções para esperar por eventos… Enfim o conceito que eu defini lá em cima de programação paralela.

1 curtida

Apenas para eu confirmar. Concorda comigo, que colocar um monte de coisa no main() terminaria deixando procedural?

Eu pensei em criar uma classe para gerenciar tudo, tipo uma classe controladora. Se é que eu conseguiria fazer isso agora. Também não sei se seria a melhor implementação…

P.S

Obrigado @Natanael.755 e @romulopb pela atenção. Eu admiro o conhecimento de vocês. Um dia chego perto.

Sim, mas você pode acabar perdendo eventos entende, se seu programa for muito rápido ele pode não receber a foto da máquina fotográfica, esse tipo de coisa. Novamente o problema não está bem definido, se você quer “simular Arduino”, até que nível, etc

1 curtida

Nesse caso será os paradigmas de Object-Oriented Programming com Event-Driven Programming, é isso?

O que desejo é controlar uns Arduinos via a interface serial deles, fazendo coleta de dados, controlando atuadores, etc.

Não é tão simples, você precisa definir o problema muito melhor, que tipo de equipamentos estão envolvidos, em que tarefa, sob quais condições…

Pode ocorrer falha na leitura que exija releitura? A coleta de dados demora sempre o mesmo tempo, ou a máquina precisa fazer alguma calibração que varia de momento para momento? A rede por onde os dados trafega é estável? Precisa ser responsivo ou apenas garantir os dados nem que demore? etc etc etc, dependendo dos problemas que cada etapa tem talvez você precise usar eventos, talvez não.

As vezes utilização de eventos funciona, mas e se o hardware envolvido não avisar quando terminou de executar completamente a tarefa? Enfim, tem muitos problemas envolvidos.

De maneira geral, num futuro mais distante eu acho que terei que usar eventos pois será algo natural: tal coisa só será feita se ocorrer um evento. Caso algo não esperado aconteça, determinadas ações terão que ser realizadas.

Mas no momento eu já faço essas coisas muito bem com código procedural. Sendo que se eu quero fazer uma modificação, tenho que mexer praticamente em tudo e escrever do zero.

Eu queria ir preparando para que a coisa escale e que seja “modular” onde eu possa implementar mais coisas sem ter que reescrever ou sem esbarrar em um problema.

Ahhhhhhh o que você quer é intercalar 2 arrays, como o @romulopb mostra python tem nativo, mas o algoritmo, consiste em percorrer o array maior e ir pegando o item baseado no indice

Você precisa fragmentar mais as coisas, em vez de:

class X:

   def ligar_todas_as_luzes():
     #liga todas as luzes

vai ter de pulverizar esses eventos:

class X:

   def ligar_luz(index):
     #liga luz no index de uma lista de luzes, possívelmente desliga outra luz se existir uma ou mais ligadas, etc

A questão é que você precisa definir melhor os problemas e pensar até onde você quer reutilizar, o que é repetitivo nos seus problemas, pois não tem como cobrir todos os casos, entende, caso contrário, bastaria uma linha de código para resolver tudo no mundo.

Por exemplo se ligar a luz, realizar alguma tarefa, desligar luz é recorrente no seus problemas, você pode fazer algo como:

async def tarefa_iluminada(lampada: Lampada, tarefa: Tarefa, apagar_depois=false):
    lampada.liga()
    resultado = await tarefa.executa() # async/await permite pausar a thread até a tarefa terminar
    if apagar_depois:
        lampada.desliga()

quero executar tarefa genérica

async def executa_tarefa(tarefa: Tarefa, lampada: Lampada=None, apagar_depois=false):
    if lampada:  
        lampada.liga()
    resultado = await tarefa.executa() # async/await permite pausar a thread até a tarefa terminar
    if  lampada && apagar_depois:
        lampada.desliga()

Você percebe quanto mais casos especiais, possibilidades de erros e afins vão surgindo quanto mais genérico vai ficando? Por isso é preciso dosar até onde vale a pena tentar reaproveitar, com que técnicas, etc. Mas só da para fazer isso pensando muito bem no problema, no que enfrentei no passado, etc.

2 curtidas

Boa, mas esse aynnc e await são uma boa pratica em python?

Depende da tarefa, se a coisa ficar muito complexa pode ser melhor procurar um framework que abstraia o assunto, mas no geral, se o problema é apenas uma questão de tarefas concorrentes e assíncronas ou síncronas, async/await é uma boa solução. Se envolver paralelismo ai será preciso recorrer a bibliotecas como Multiprocessing e afins.

Por exemplo, para eventos ao estilo ReactiveX, asyncio com uso de async/await funciona muito bem para modelar coisas como streams, pipes, etc.

1 curtida

Eu vou tirar um dia para repensar o problema e ver o que posso fazer. Talvez consiga uma solução apenas mudando a forma de ver o problema. Mas valeu muito a pena pois vi um monte de coisas novas e essa ideia de assíncrono, eventos. Isso parece muito legal de se trabalhar.

Programar realmente é uma arte. :slight_smile:

É bem interessante mesmo, permite resolver problemas mais sofisticados mas ao mesmo tempo traz vários problemas a se considerar, você precisa pensar em muito mais possíveis condições e problemas durante a execução.

por exemplo se eu fizer algo como:

import asyncio
from random import uniform

async def a():
    print('comecando a')
    await asyncio.sleep(uniform(0, 3))
    print ('termina a')

async def b():
    print('comecando b')
    await asyncio.sleep(uniform(0, 3))
    print ('termina b')
    

posso terminar com resultados estranhos como o seguinte:

async def execution_a():
    a_task = asyncio.create_task(a())
    await b()
    print('fim')
    await a_task
    
asyncio.run(execution_a())

# comecando b
# comecando a
# termina b
# fim
# termina a

async def execution_b():
    asyncio.create_task(a())
    await b()
    print('fim')
    
asyncio.run(execution_b())

# comecando b
# comecando a
# termina a
# termina b
# fim

async def execution_c():
    await a()
    task = asyncio.create_task(b())
    print('fim')
    
asyncio.run(execution_c())

# comecando a
# termina a
# fim
# comecando b

O que eu já aprendi é que vale mais ficar dois dias pensando no workflow do programa e desenhando em papel, do que sair escrevendo uma função na primeira meia hora.

Com uma base no papel fica mais fácil organizar o código. Fica melhor de pensar “essa etapa da roda, vou colocar na montagem da carroceria ou na montagem final”? Daí vc ve que fica melhor montar antes porque daí o carro já consegue ir rodando pra próxima etapa. Se vc começa direto no código, acaba lembrando da roda na parte do motor e acaba colocando a montagem da roda lá na etapa do motor porque está “atolado” no código e perdeu a visão do processo global. Depois quando vai pra parte da montagem final lembra que a a roda tá lá no outro arquivo e seria melhor passar pra atual etapa (e lá se vai mais um tempo pra refactor).

1 curtida