Milan Herda, 06 / 2023
SOLID princípy v JavaScripte
Skratka predstavujúca 5 základných princípov dobrého softvérového návrhu
Interfejsy by mali byť jemne granulované a špecifické pre klienta
Klient je kód používajúci iný kód
interface UserDocument {
id: number;
name: string;
type: string;
width?: number;
height?: number;
}
interface UserDocument {
id: number;
name: string;
type: string;
width?: number;
height?: number;
}
function Files({ files }: { files: UserDocument[] }) {
return (
<ul>
{files.map((file) => {
return <li key={file.id}>{file.name}</li>;
})}
</ul>
);
}
interface UserDocument {
id: number;
name: string;
type: string;
width?: number;
height?: number;
}
function Files({ files }: { files: UserDocument[] }) {
return (
<ul>
{files.map((file) => {
return <li key={file.id}>{file.name}</li>;
})}
</ul>
);
}
function Images({ images }: { images: UserDocument[] }) {
return (
<ul>
{images.map((image) => {
return (
<li key={image.id}>
{image.name} {image.width}x{image.height}
</li>
);
})}
</ul>
);
}
Časté riešenie je pridanie has/can/is metódy alebo property
interface UserDocument {
id: number;
name: string;
type: string;
isImage: boolean;
width?: number;
height?: number;
}
function Images({ images }: { images: UserDocument[] }) {
return (
<ul>
{images.filter(i => i.isImage).map((image) => {
return (
<li key={image.id}>
{image.name} {image.width}x{image.height}
</li>
);
})}
</ul>
);
}
Časté riešenie je pridanie has/can/is metódy alebo property
Takéto riešenie nie je dobré, lebo:
Upravte kód tak, aby si komponent Images mohol byť istý, že môže bez kontrol pristúpiť k width a height
Zdrojové súbory:
Výsledok
// src/services/documents/types.ts
export interface UserDocument {
id: number;
name: string;
type: string;
}
export interface ImageDocument extends UserDocument {
width: number;
height: number;
}
Výsledok
// src/components/documents/Images.tsx
import { ImageDocument } from '@local/services/documents/types';
function Images({ images }: { images: ImageDocument[] }) {
return (
<ul>
{images.map((image) => {
return (
<li key={image.id}>
{image.name} {image.width}x{image.height}
</li>
);
})}
</ul>
);
}
export default Images;
interface ChannelRegistry {
setChannel: (newChannel: string) => void;
getChannel: () => string;
}
function createDetector(registry: ChannelRegistry) {
function detect() {
// ...
registry.setChannel(channel);
}
// ...
}
function ChannelProvider({ registry, children }: PropsWithChildren<{
registry: ChannelRegistry
}>) {
const contextData: ChannelContextData = {
channel: registry.getChannel(),
};
// ....
}
Problém: klienti požadujú funkcionalitu, s ktorou nepracujú
Upravte kód tak, aby každý klient vyžadoval iba to, čo potrebuje
// src/services/channel/types.ts
export interface ChannelProvider {
getChannel: () => string;
}
export interface ChannelRegistry extends ChannelProvider {
setChannel: (newChannel: string) => void;
}
Riešenie
// src/components/channel/ChannelContext
export function ChannelContextProvider(
{ registry, children }:
PropsWithChildren<{
registry: ChannelProvider
}>
) {
const contextData: ChannelContextData = {
channel: registry.getChannel(),
};
return (
<ChannelContext.Provider value={contextData}>
{children}
</ChannelContext.Provider>
);
}
Riešenie
src/entity/user.ts
export interface User {
id: number;
firstname: string;
lastname: string;
email: string;
phone: string;
address: {
street: string;
city: string;
zipCode: string;
};
education: {
schoolName: string;
yearFrom: number;
yearTo?: number;
}[];
workExperience: {
companyName: string;
position: string;
description: string;
yearFrom: number;
yearTo?: number;
}[];
hobby: string;
additionalInfo: string;
}
src/components/user/LoggedInUserInfo.tsx
import { User } from '@local/entity/user';
function LoggedInUserInfo({ user }: { user: User }) {
const name = `${user.firstname} ${user.lastname}`;
return (
<span>
{name} {user.email}
</span>
);
}
export default LoggedInUserInfo;
Problém: klient požaduje funkcionalitu, s ktorou nepracuje
Upravte kód tak, aby klient vyžadoval iba to, čo potrebuje
src/components/user/LoggedInUserInfo.tsx
import { User } from '@local/entity/user';
type LoggedInUserInfoProps = Pick<User, 'firstname' | 'lastname' | 'email'>;
function LoggedInUserInfo({ user }: { user: LoggedInUserInfoProps }) {
const name = `${user.firstname} ${user.lastname}`;
return (
<span>
{name} {user.email}
</span>
);
}
export default LoggedInUserInfo;
src/components/user/LoggedInUserInfo.tsx
interface LoggedInUserInfoProps {
firstname: string;
lastname: string;
email: string;
}
function LoggedInUserInfo({ user }: { user: LoggedInUserInfoProps }) {
const name = `${user.firstname} ${user.lastname}`;
return (
<span>
{name} {user.email}
</span>
);
}
export default LoggedInUserInfo;
Výhody použitia utility Pick
Nevýhody použitia utility Pick
Výhody a nevýhody si musíme vždy zvážiť a nepoužívať naslepo jednu alebo druhú variantu.
Interfejsy by mali byť jemne granulované a špecifické pre klienta
Klient je kód používajúci iný kód