-
-
Notifications
You must be signed in to change notification settings - Fork 277
Expand file tree
/
Copy pathintroduction.texy
More file actions
526 lines (375 loc) · 18.9 KB
/
introduction.texy
File metadata and controls
526 lines (375 loc) · 18.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
O que é Injeção de Dependência?
*******************************
.[perex]
Este capítulo apresentará os procedimentos básicos de programação que você deve seguir ao escrever todas as aplicações. São os fundamentos necessários para escrever código limpo, compreensível e sustentável.
Se você dominar e seguir estas regras, o Nette o apoiará em cada passo. Ele cuidará das tarefas rotineiras para você e fornecerá o máximo de conforto, para que você possa se concentrar na lógica em si.
Os princípios que mostraremos aqui são bastante simples. Você não precisa se preocupar com nada.
Lembra do seu primeiro programa?
--------------------------------
Não sabemos em que linguagem você o escreveu, mas se fosse PHP, provavelmente seria algo assim:
```php
function soucet(float $a, float $b): float
{
return $a + $b;
}
echo soucet(23, 1); // imprime 24
```
Algumas linhas triviais de código, mas nelas se escondem tantos conceitos-chave. Que existem variáveis. Que o código é dividido em unidades menores, como funções. Que passamos argumentos de entrada para elas e elas retornam resultados. Faltam apenas condições e loops.
O fato de passarmos dados de entrada para uma função e ela retornar um resultado é um conceito perfeitamente compreensível, usado também em outras áreas, como na matemática.
Uma função tem sua assinatura, que consiste em seu nome, uma lista de parâmetros e seus tipos, e finalmente o tipo do valor de retorno. Como usuários, estamos interessados na assinatura; geralmente não precisamos saber nada sobre a implementação interna.
Agora imagine que a assinatura da função fosse assim:
```php
function soucet(float $x): float
```
Soma com um parâmetro? Isso é estranho... E que tal assim?
```php
function soucet(): float
```
Isso já é muito estranho, não é? Como a função seria usada?
```php
echo soucet(); // o que será que imprime?
```
Ao olhar para tal código, ficaríamos confusos. Não apenas um iniciante não entenderia, mas nem mesmo um programador experiente entenderia tal código.
Você está pensando como essa função seria por dentro? Onde ela obteria os operandos? Provavelmente, ela os obteria *de alguma forma* por conta própria, talvez assim:
```php
function soucet(): float
{
$a = Input::get('a');
$b = Input::get('b');
return $a + $b;
}
```
No corpo da função, descobrimos ligações ocultas a outras funções globais ou métodos estáticos. Para descobrir de onde os operandos realmente vêm, precisamos investigar mais.
Não por aqui!
-------------
O design que acabamos de mostrar é a essência de muitas características negativas:
- a assinatura da função fingia não precisar de operandos, o que nos confundiu
- não sabemos como fazer a função somar outros dois números
- tivemos que olhar o código para descobrir onde ela obtém os operandos
- descobrimos ligações ocultas
- para entender completamente, é necessário examinar também essas ligações
E é tarefa da função de soma obter as entradas? Claro que não. Sua responsabilidade é apenas a soma em si.
Não queremos encontrar tal código, e definitivamente não queremos escrevê-lo. A correção é simples: voltar ao básico e simplesmente usar parâmetros:
```php
function soucet(float $a, float $b): float
{
return $a + $b;
}
```
Regra nº 1: peça para ser passado
---------------------------------
A regra mais importante é: **todos os dados que uma função ou classe precisa devem ser passados para ela**.
Em vez de inventar maneiras ocultas pelas quais eles poderiam obtê-los sozinhos, simplesmente passe os parâmetros. Você economizará o tempo necessário para inventar caminhos ocultos, que definitivamente não melhorarão seu código.
Se você seguir esta regra sempre e em toda parte, estará no caminho para um código sem ligações ocultas. Para um código que é compreensível não apenas para o autor, mas também para qualquer pessoa que o leia depois dele. Onde tudo é compreensível a partir das assinaturas das funções e classes e não há necessidade de procurar segredos ocultos na implementação.
Essa técnica é tecnicamente chamada de **injeção de dependência**. E esses dados são chamados de **dependências.** Na verdade, é apenas a passagem comum de parâmetros, nada mais.
.[note]
Por favor, não confunda injeção de dependência, que é um padrão de projeto, com "contêiner de injeção de dependência", que é uma ferramenta, ou seja, algo diametralmente diferente. Falaremos sobre contêineres mais tarde.
De funções para classes
-----------------------
E como as classes se relacionam com isso? Uma classe é uma unidade mais complexa do que uma função simples, mas a regra nº 1 se aplica integralmente aqui também. Apenas existem [mais opções para passar argumentos|passing-dependencies]. Por exemplo, de forma bastante semelhante ao caso de uma função:
```php
class Matematika
{
public function soucet(float $a, float $b): float
{
return $a + $b;
}
}
$math = new Matematika;
echo $math->soucet(23, 1); // 24
```
Ou usando outros métodos, ou diretamente o construtor:
```php
class Soucet
{
public function __construct(
private float $a,
private float $b,
) {
}
public function spocti(): float
{
return $this->a + $this->b;
}
}
$soucet = new Soucet(23, 1);
echo $soucet->spocti(); // 24
```
Ambos os exemplos estão totalmente de acordo com a injeção de dependência.
Exemplos reais
--------------
No mundo real, você não escreverá classes para somar números. Vamos passar para exemplos práticos.
Temos uma classe `Article` representando um artigo de blog:
```php
class Article
{
public int $id;
public string $title;
public string $content;
public function save(): void
{
// salvamos o artigo no banco de dados
}
}
```
e o uso será o seguinte:
```php
$article = new Article;
$article->title = '10 coisas que você precisa saber sobre perder peso';
$article->content = 'Todo ano milhões de pessoas em ...';
$article->save();
```
O método `save()` salva o artigo em uma tabela do banco de dados. Implementá-lo usando [Nette Database |database:] seria moleza, se não fosse por um obstáculo: onde `Article` obtém a conexão com o banco de dados, ou seja, o objeto da classe `Nette\Database\Connection`?
Parece que temos muitas opções. Pode obtê-lo de algum lugar em uma variável estática. Ou herdar de uma classe que fornece a conexão com o banco de dados. Ou usar o chamado [singleton |global-state#Singleton]. Ou as chamadas facades, que são usadas no Laravel:
```php
use Illuminate\Support\Facades\DB;
class Article
{
public int $id;
public string $title;
public string $content;
public function save(): void
{
DB::insert(
'INSERT INTO articles (title, content) VALUES (?, ?)',
[$this->title, $this->content],
);
}
}
```
Ótimo, resolvemos o problema.
Ou não?
Lembre-se da [##Regra nº 1: peça para ser passado]: todas as dependências que a classe precisa devem ser passadas para ela. Porque se quebrarmos a regra, entramos no caminho do código sujo cheio de ligações ocultas, incompreensibilidade, e o resultado será uma aplicação que será dolorosa de manter e desenvolver.
O usuário da classe `Article` não tem ideia de onde o método `save()` salva o artigo. Em uma tabela do banco de dados? Em qual, produção ou teste? E como isso pode ser alterado?
O usuário precisa olhar como o método `save()` é implementado e encontra o uso do método `DB::insert()`. Então, ele precisa investigar mais, como esse método obtém a conexão com o banco de dados. E as ligações ocultas podem formar uma cadeia bastante longa.
Em código limpo e bem projetado, nunca existem ligações ocultas, facades do Laravel ou variáveis estáticas. Em código limpo e bem projetado, os argumentos são passados:
```php
class Article
{
public function save(Nette\Database\Connection $db): void
{
$db->query('INSERT INTO articles', [
'title' => $this->title,
'content' => $this->content,
]);
}
}
```
Ainda mais prático, como veremos mais adiante, será pelo construtor:
```php
class Article
{
public function __construct(
private Nette\Database\Connection $db,
) {
}
public function save(): void
{
$this->db->query('INSERT INTO articles', [
'title' => $this->title,
'content' => $this->content,
]);
}
}
```
.[note]
Se você é um programador experiente, pode estar pensando que `Article` não deveria ter um método `save()`, deveria representar puramente um componente de dados e o armazenamento deveria ser responsabilidade de um repositório separado. Isso faz sentido. Mas isso nos levaria muito além do escopo do tópico, que é a injeção de dependência, e do esforço para fornecer exemplos simples.
Se você for escrever uma classe que requer, por exemplo, um banco de dados para sua operação, não invente de onde obtê-lo, mas peça para que seja passado. Talvez como um parâmetro do construtor ou de outro método. Admita as dependências. Admita-as na API da sua classe. Você obterá um código compreensível e previsível.
E que tal esta classe, que registra mensagens de erro:
```php
class Logger
{
public function log(string $message)
{
$file = LOG_DIR . '/log.txt';
file_put_contents($file, $message . "\n", FILE_APPEND);
}
}
```
O que você acha, seguimos a [##Regra nº 1: peça para ser passado]?
Não seguimos.
A informação chave, ou seja, o diretório com o arquivo de log, a classe *obtém por si mesma* a partir de uma constante.
Veja o exemplo de uso:
```php
$logger = new Logger;
$logger->log('A temperatura é 23 °C');
$logger->log('A temperatura é 10 °C');
```
Sem conhecer a implementação, você conseguiria responder à pergunta de onde as mensagens são escritas? Você pensaria que para funcionar é necessária a existência da constante `LOG_DIR`? E você conseguiria criar uma segunda instância que escreveria em outro lugar? Certamente não.
Vamos corrigir a classe:
```php
class Logger
{
public function __construct(
private string $file,
) {
}
public function log(string $message): void
{
file_put_contents($this->file, $message . "\n", FILE_APPEND);
}
}
```
A classe agora é muito mais compreensível, configurável e, portanto, mais útil.
```php
$logger = new Logger('/caminho/para/log.txt');
$logger->log('A temperatura é 15 °C');
```
Mas isso não me interessa!
--------------------------
*"Quando crio um objeto Article e chamo save(), não quero lidar com o banco de dados, só quero que ele seja salvo naquele que configurei."*
*"Quando uso o Logger, só quero que a mensagem seja escrita, e não quero me preocupar onde. Que use a configuração global."*
Essas são observações válidas.
Como exemplo, mostraremos uma classe que envia newsletters e registra o resultado:
```php
class NewsletterDistributor
{
public function distribute(): void
{
$logger = new Logger(/* ... */);
try {
$this->sendEmails();
$logger->log('E-mails foram enviados');
} catch (Exception $e) {
$logger->log('Ocorreu um erro ao enviar');
throw $e;
}
}
}
```
O `Logger` aprimorado, que não usa mais a constante `LOG_DIR`, requer que o caminho do arquivo seja especificado no construtor. Como resolver isso? A classe `NewsletterDistributor` não se importa onde as mensagens são escritas, ela só quer escrevê-las.
A solução é novamente a [##Regra nº 1: peça para ser passado]: todos os dados que a classe precisa, nós passamos para ela.
Então isso significa que passamos o caminho do log através do construtor, que então usamos ao criar o objeto `Logger`?
```php
class NewsletterDistributor
{
public function __construct(
private string $file, // ⛔ ASSIM NÃO!
) {
}
public function distribute(): void
{
$logger = new Logger($this->file);
```
Assim não! O caminho, de fato, **não pertence** aos dados que a classe `NewsletterDistributor` precisa; esses são necessários pelo `Logger`. Você percebe a diferença? A classe `NewsletterDistributor` precisa do logger como tal. Então, passamos ele:
```php
class NewsletterDistributor
{
public function __construct(
private Logger $logger, // ✅
) {
}
public function distribute(): void
{
try {
$this->sendEmails();
$this->logger->log('E-mails foram enviados');
} catch (Exception $e) {
$this->logger->log('Ocorreu um erro ao enviar');
throw $e;
}
}
}
```
Agora está claro pelas assinaturas da classe `NewsletterDistributor` que o log faz parte de sua funcionalidade. E a tarefa de trocar o logger por outro, talvez para testes, é completamente trivial. Além disso, se o construtor da classe `Logger` mudar, isso não terá nenhum efeito em nossa classe.
Regra nº 2: pegue o que é seu
-----------------------------
Não se deixe enganar e não peça para passar as dependências de suas dependências. Peça para passar apenas suas dependências.
Graças a isso, o código que utiliza outros objetos será completamente independente das mudanças em seus construtores. Sua API será mais verdadeira. E, principalmente, será trivial trocar essas dependências por outras.
Novo membro da família
----------------------
Na equipe de desenvolvimento, foi decidido criar um segundo logger, que escreve no banco de dados. Criaremos então a classe `DatabaseLogger`. Então temos duas classes, `Logger` e `DatabaseLogger`, uma escreve em arquivo, a outra no banco de dados... não parece algo estranho nessa nomenclatura? Não seria melhor renomear `Logger` para `FileLogger`? Certamente sim.
Mas faremos isso de forma inteligente. Sob o nome original, criaremos uma interface:
```php
interface Logger
{
function log(string $message): void;
}
```
... que ambos os loggers implementarão:
```php
class FileLogger implements Logger
// ...
class DatabaseLogger implements Logger
// ...
```
E graças a isso, não será necessário alterar nada no restante do código onde o logger é utilizado. Por exemplo, o construtor da classe `NewsletterDistributor` continuará satisfeito em exigir `Logger` como parâmetro. E caberá a nós qual instância passar para ele.
**Por isso, nunca damos aos nomes das interfaces o sufixo `Interface` ou o prefixo `I`.** Caso contrário, não seria possível desenvolver o código de forma tão elegante.
Houston, temos um problema
--------------------------
Enquanto em toda a aplicação podemos nos contentar com uma única instância de logger, seja de arquivo ou de banco de dados, e simplesmente passá-la para todos os lugares onde algo é registrado, a situação é bem diferente no caso da classe `Article`. Suas instâncias são criadas conforme necessário, até mesmo várias vezes. Como lidar com a dependência do banco de dados em seu construtor?
Como exemplo, pode servir um controller que, após o envio de um formulário, deve salvar o artigo no banco de dados:
```php
class EditController extends Controller
{
public function formSubmitted($data)
{
$article = new Article(/* ... */);
$article->title = $data->title;
$article->content = $data->content;
$article->save();
}
}
```
Uma solução possível se oferece diretamente: passamos o objeto do banco de dados pelo construtor para `EditController` e usamos `$article = new Article($this->db)`.
Assim como no caso anterior com `Logger` e o caminho do arquivo, este não é o procedimento correto. O banco de dados não é uma dependência de `EditController`, mas de `Article`. Passar o banco de dados, portanto, vai contra a [#regra nº 2: pegue o que é seu]. Quando o construtor da classe `Article` mudar (um novo parâmetro for adicionado), será necessário modificar também o código em todos os lugares onde instâncias são criadas. Ufa.
Houston, o que você sugere?
Regra nº 3: deixe para a fábrica
--------------------------------
Ao eliminar as ligações ocultas e passar todas as dependências como argumentos, obtivemos classes mais configuráveis e flexíveis. E, portanto, precisamos de algo mais, que crie e configure essas classes mais flexíveis para nós. Chamaremos isso de fábricas.
A regra é: se uma classe tem dependências, deixe a criação de suas instâncias para a fábrica.
As fábricas são substitutos mais inteligentes do operador `new` no mundo da injeção de dependência.
.[note]
Por favor, não confunda com o padrão de projeto *factory method*, que descreve um uso específico de fábricas e não está relacionado a este tópico.
Fábrica
-------
Uma fábrica é um método ou classe que produz e configura objetos. A classe que produz `Article` chamaremos de `ArticleFactory` e poderia parecer, por exemplo, assim:
```php
class ArticleFactory
{
public function __construct(
private Nette\Database\Connection $db,
) {
}
public function create(): Article
{
return new Article($this->db);
}
}
```
Seu uso no controller será o seguinte:
```php
class EditController extends Controller
{
public function __construct(
private ArticleFactory $articleFactory,
) {
}
public function formSubmitted($data)
{
// deixamos a fábrica criar o objeto
$article = $this->articleFactory->create();
$article->title = $data->title;
$article->content = $data->content;
$article->save();
}
}
```
Neste momento, se a assinatura do construtor da classe `Article` mudar, a única parte do código que precisa reagir é a própria fábrica `ArticleFactory`. Todo o restante do código que trabalha com objetos `Article`, como `EditController`, não será afetado de forma alguma.
Talvez você esteja batendo na testa agora, se realmente nos ajudamos. A quantidade de código aumentou e tudo começa a parecer suspeitosamente complicado.
Não se preocupe, em breve chegaremos ao Contêiner de DI do Nette. E ele tem vários ases na manga que simplificarão imensamente a construção de aplicações usando injeção de dependência. Por exemplo, em vez da classe `ArticleFactory`, será suficiente [escrever apenas uma interface |factory]:
```php
interface ArticleFactory
{
function create(): Article;
}
```
Mas estamos nos adiantando, aguarde mais um pouco :-)
Resumo
------
No início deste capítulo, prometemos mostrar um procedimento para projetar código limpo. Basta para as classes
1) [passar as dependências que precisam |#Regra nº 1: peça para ser passado |#pravidlo č. 1: nech si to předat]
2) [e, inversamente, não passar o que não precisam diretamente |#Regra nº 2: pegue o que é seu |#Pravidlo č. 2: ber, co tvé jest]
3) [e que objetos com dependências são melhor criados em fábricas |#Regra nº 3: deixe para a fábrica |#Pravidlo č. 3: nech to na továrně]
Pode não parecer à primeira vista, mas essas três regras têm consequências de longo alcance. Elas levam a uma visão radicalmente diferente do design de código. Vale a pena? Programadores que abandonaram velhos hábitos e começaram a usar consistentemente a injeção de dependência consideram este passo um momento crucial em suas vidas profissionais. Abriu-se para eles o mundo de aplicações claras e sustentáveis.
Mas e se o código não usar consistentemente a injeção de dependência? E se for construído sobre métodos estáticos ou singletons? Isso traz algum problema? [Traz e muito fundamentais |global-state].