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:
A maquina pega a corroceria e coloca no trilho.
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.
A máquina de colocar rodas recebe uma caixa com 4 rodas e coloca no eixo.
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”
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.
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.
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
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.
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.
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
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.
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.
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.
É 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.
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).