Milan Herda, 07 / 2023
SOLID princípy v JavaScripte
Milan Herda, 07 / 2023
SOLID princípy v JavaScripte
Milan Herda, 07 / 2023
SOLID princípy v TypeScripte
a v Reacte
Skratka predstavujúca 5 základných princípov dobrého softvérového návrhu
Ak váš kód potrebuje k svojej činnosti iný objekt (funkciu/triedu), tak tento objekt je závislosťou pre váš kód.
Najviac viditeľné sú parametre funkcií, property tried a importy modulov
Je jednoduchšie vymeniť inštanciu niečoho abstraktného, ako konkrétnu vec
Zdrojové súbory:
class MojKamarat
{
// ...
}
interface StatnaZakazka
{
priradDodavatela: (dodavatel: MojKamarat) => void;
// ...
}
interface SplnajuciPodmienky
{
// ...
}
interface StatnaZakazka
{
priradDodavatela: (dodavatel: SplnajuciPodmienky) => void;
// ...
}
Nie je to takto lepšie?
function CharactersPage() {
const [characters, setCharacters] = useState<Character[]>([]);
const { restConnection } = useContext(RestConnectionContext);
useEffect(() => {
async function fetchData() {
const loadedCharacters = await restConnection.loadJsonData<Character[]>(
'/api/list.json'
);
setCharacters(loadedCharacters);
}
fetchData();
}, []);
return (
<>
<h2>List of Characters</h2>
<ul>
{characters.map((character: Character) => {
return <li key={character.id}>{character.name}</li>;
})}
</ul>
</>
);
}
Komponent zobrazuje dáta a priamo získava dáta. To je priveľa zodpovedností.
/src/ui/pages/CharactersPage.tsx
function CharactersPage() {
const [characters, setCharacters] = useState<Character[]>([]);
const { restConnection } = useContext(RestConnectionContext);
useEffect(() => {
async function fetchData() {
const loadedCharacters = await restConnection.loadJsonData<Character[]>(
'/api/list.json'
);
setCharacters(loadedCharacters);
}
fetchData();
}, []);
return (
<>
<h2>List of Characters</h2>
<ul>
{characters.map((character: Character) => {
return <li key={character.id}>{character.name}</li>;
})}
</ul>
</>
);
}
URL je tu napevno, to je signál porušenia Open-Closed princípu
/src/ui/pages/CharactersPage.tsx
function CharactersPage() {
const [characters, setCharacters] = useState<Character[]>([]);
const { restConnection } = useContext(RestConnectionContext);
useEffect(() => {
async function fetchData() {
const loadedCharacters = await restConnection.loadJsonData<Character[]>(
'/api/list.json'
);
setCharacters(loadedCharacters);
}
fetchData();
}, []);
return (
<>
<h2>List of Characters</h2>
<ul>
{characters.map((character: Character) => {
return <li key={character.id}>{character.name}</li>;
})}
</ul>
</>
);
}
Potrebuje komponent vedieť, že dáta sú dostupné cez REST?
Nezávisí tak príliš na konkrétnosti?
/src/ui/pages/CharactersPage.tsx
RestConnection je interface, je to stále problém?
function CharactersPage() {
const characters = useCharacters();
return (
<>
<h2>List of Characters</h2>
<ul>
{characters.map((character: Character) => {
return <li key={character.id}>{character.name}</li>;
})}
</ul>
</>
);
}
/src/ui/pages/CharactersPage.tsx
function useCharacters() {
const [characters, setCharacters] = useState<Character[]>([]);
const { restConnection } = useContext(RestConnectionContext);
useEffect(() => {
async function fetchData() {
const loadedCharacters =
await restConnection.loadJsonData<Character[]>(
'/api/list.json'
);
setCharacters(loadedCharacters);
}
fetchData();
}, []);
return characters;
}
/src/ui/hooks/useCharacters.ts
function useCharacters() {
return useRestData<Character[]>('/api/list.json', []);
}
/src/ui/hooks/useCharacters.ts
function useRestData<T>(sourceUrl: string, initialData: T) {
const [data, setData] = useState<T>(initialData);
const { restConnection } = useContext(RestConnectionContext);
useEffect(() => {
async function fetchData() {
const loadedData =
await restConnection.loadJsonData<T>(sourceUrl);
setData(loadedData);
}
fetchData();
}, []);
return data;
}
/src/ui/hooks/useRestData.ts
function CharactersPage() {
const characters = useCharacters();
return (
<>
<h2>List of Characters</h2>
<ul>
{characters.map((character: Character) => {
return <li key={character.id}>{character.name}</li>;
})}
</ul>
</>
);
}
/src/ui/pages/CharactersPage.tsx
Komponent už nemá závislosť na RestConnection, nemáme čo riešiť
Komponent už nemá závislosť na RestConnection...
To je síce pravda, ale problém je len presunutý.
Hooky useCharacters a useRestData oba vedia, že pracujú s RestConnection alebo REST API.
Nepotrebujú to vedieť a vieme to urobiť lepšie.
Musíme sa ale v zmenách vrátiť...
function useCharacters() {
const [characters, setCharacters] = useState<Character[]>([]);
const { restConnection } = useContext(RestConnectionContext);
useEffect(() => {
async function fetchData() {
const loadedCharacters =
await restConnection.loadJsonData<Character[]>(
'/api/list.json'
);
setCharacters(loadedCharacters);
}
fetchData();
}, []);
return characters;
}
/src/ui/hooks/useCharacters.ts
Čo keby sme mali službu, ktorá nám poskytuje prístup k postavám a tu ju len poprosíme o dáta?
function useCharacters() {
const [characters, setCharacters] = useState<Character[]>([]);
const { characterRepository } = useContext(CharacterRepositoryContext);
useEffect(() => {
async function fetchData() {
const loadedData = await characterRepository.loadCharacters();
setCharacters(loadedData);
}
fetchData();
}, []);
return characters;
}
/src/ui/hooks/useCharacters.ts
Je jednoduchšie vymeniť inštanciu niečoho abstraktného, ako konkrétnu vec
Naprogramujte tzv. FizzBuzz generátor, ktorý:
Upravte váš FizzBuzz generátor:
export interface Rule {
doesMatch: (num: number) => boolean;
getReplacement: (num: number) => number | string;
}
const fizzRule: Rule = {
doesMatch: (num) => num % 3 === 0,
getReplacement: () => 'Fizz',
};
export function createGenerator() {
const rules: Rule[] = [];
function getItem(num: number) {
for (const rule of rules) {
if (rule.doesMatch(num)) {
return rule.getReplacement(num);
}
}
return num;
}
return {
registerRule(rule: Rule) {
rules.push(rule);
return this;
},
generate(min: number, max: number) {
const items: (number | string)[] = [];
for (let i = min; i <= max; i += 1) {
items.push(getItem(i));
}
return items;
},
};
const generator = createGenerator();
generator.registerRule(fizzBuzzRule);
generator.registerRule(fizzRule);
generator.registerRule(buzzRule);
// ...
const list = generator.generate(1, 50);
Riešenie:
Aby bol kód ľahko udržiavateľný, testovateľný a rozširovateľný s minimálnym množstvom programovania, tak každá jednotka kódu by mala: