Kaique Mitsuo Silva Yamamoto
Arquitetura softwareFrontend webTypeScriptClean CodeSOLID

I — Interface Segregation Principle

Ninguém deveria ser forçado a depender de métodos que não usa. Interfaces pequenas e específicas em TypeScript full-stack.

I — Interface Segregation Principle (ISP)

"Nenhum cliente deve ser forçado a depender de métodos que não utiliza." — Robert C. Martin

O que é, em português claro

Imagine um controle remoto universal com 200 botões — para TV, ar-condicionado, som, cortina elétrica, garagem. Você usa 5 botões para a TV. Mas o controle tem 195 botões que você nunca toca e que só complicam a vida.

ISP diz: prefira interfaces pequenas e específicas a uma interface grande e genérica. Quem precisa de TV usa o controle de TV. Quem precisa de ar usa o controle de ar.

Onde acontece na vida real

// ❌ Interface monolítica — força TODOS os serviços a implementar tudo
interface IService {
  create(data: any): Promise<any>;
  read(id: string): Promise<any>;
  readAll(): Promise<any[]>;
  update(id: string, data: any): Promise<any>;
  delete(id: string): Promise<void>;
  export(format: 'csv' | 'pdf'): Promise<Buffer>;
  import(file: Buffer): Promise<void>;
  subscribe(id: string, callback: Function): void;
  getAnalytics(id: string): Promise<Analytics>;
}

// Um serviço de "Configurações do sistema" precisa de create/read/update
// Mas é FORÇADO a implementar export, import, subscribe, analytics
// Mesmo que nunca use — e o TypeScript exige que implemente

Exemplo prático — Frontend (React)

❌ Componente que recebe props que não usa

// Props monolítica — todo componente de lista recebe TUDO
interface ListProps<T> {
  items: T[];
  renderItem: (item: T) => React.ReactNode;
  onEdit: (item: T) => void;
  onDelete: (item: T) => void;
  onExport: (items: T[]) => void;
  onImport: (file: File) => void;
  onBulkAction: (action: string, items: T[]) => void;
  showSearch: boolean;
  showPagination: boolean;
  showFilters: boolean;
}

// Lista de países — só precisa mostrar items
// Mas é FORÇADA a receber onEdit, onDelete, onImport...
function CountryList({ items, renderItem }: ListProps<Country>) {
  // onEdit, onDelete, onExport, onImport... desestruturados mas ignorados
  // Quem chama precisa passar tudo, mesmo que seja () => {}
  return <ul>{items.map(item => <li>{renderItem(item)}</li>)}</ul>;
}

✅ Interfaces compostas e específicas

// Interfaces pequenas — cada uma representa uma capacidade
interface Listable<T> {
  items: T[];
  renderItem: (item: T) => React.ReactNode;
}

interface Editable<T> {
  onEdit: (item: T) => void;
  onDelete: (item: T) => void;
}

interface Exportable<T> {
  onExport: (items: T[]) => void;
  onImport: (file: File) => void;
}

interface Searchable {
  showSearch: boolean;
  searchTerm: string;
  onSearch: (term: string) => void;
}

// Composição — o componente recebe só o que precisa
function SimpleList<T>({ items, renderItem }: Listable<T>) {
  return <ul>{items.map(item => <li>{renderItem(item)}</li>)}</ul>;
}

function EditableList<T>({ items, renderItem, onEdit, onDelete }: Listable<T> & Editable<T>) {
  return (
    <ul>
      {items.map(item => (
        <li>
          {renderItem(item)}
          <button onClick={() => onEdit(item)}>Editar</button>
          <button onClick={() => onDelete(item)}>Remover</button>
        </li>
      ))}
    </ul>
  );
}

// Uso — cada componente recebe só o que precisa
<SimpleList items={countries} renderItem={c => c.name} />
<EditableList items={users} renderItem={u => u.name} onEdit={editUser} onDelete={deleteUser} />

Exemplo prático — Backend (NestJS)

❌ Repository com interface inchada

// Interface única para todos os repositories
interface IRepository<T> {
  findAll(filter?: Filter<T>): Promise<T[]>;
  findById(id: string): Promise<T | null>;
  findOne(filter: Filter<T>): Promise<T | null>;
  create(data: Partial<T>): Promise<T>;
  createMany(data: Partial<T>[]): Promise<T[]>;
  update(id: string, data: Partial<T>): Promise<T>;
  delete(id: string): Promise<void>;
  count(filter?: Filter<T>): Promise<number>;
  aggregate(pipeline: any[]): Promise<any[]>;
  watch(callback: (change: any) => void): void;
  createIndexes(indexes: any[]): Promise<void>;
}

// AuditLog só precisa de create e findAll
// Mas é FORÇADO a implementar update, delete, watch, aggregate...
class AuditLogRepository implements IRepository<AuditLog> {
  async update(id: string, data: Partial<AuditLog>): Promise<AuditLog> {
    throw new Error('Audit logs não podem ser atualizados'); // ❌ Método morto
  }
  async delete(id: string): Promise<void> {
    throw new Error('Audit logs não podem ser deletados'); // ❌ Método morto
  }
  // ... mais 9 métodos que nunca deveriam existir aqui
}

✅ Interfaces segmentadas por capacidade

// Cada interface representa UMA capacidade
interface Readable<T> {
  findById(id: string): Promise<T | null>;
  findAll(filter?: Filter<T>): Promise<T[]>;
}

interface Writable<T> {
  create(data: Partial<T>): Promise<T>;
  update(id: string, data: Partial<T>): Promise<T>;
}

interface Deletable<T> {
  delete(id: string): Promise<void>;
}

interface Countable {
  count(filter?: Filter<any>): Promise<number>;
}

interface Aggregatable {
  aggregate(pipeline: any[]): Promise<any[]>;
}

// Composição — cada repository implementa só o que precisa
class AuditLogRepository implements Readable<AuditLog>, Writable<AuditLog> {
  // Só lê e cria — não deleta, não agrega, não faz watch
  // E o TypeScript NÃO exige os outros métodos
}

class UserRepository implements Readable<User>, Writable<User>, Deletable<User>, Countable {
  // CRUD completo + contagem — não agrega nem faz watch
}

class AnalyticsRepository implements Readable<Analytics>, Aggregatable {
  // Lê e agrega — não escreve nem deleta
}
// Service que depende só do que precisa — não vê métodos que não usa
class AuditService {
  constructor(
    private logs: Readable<AuditLog> & Writable<AuditLog>, // só precisa ler e criar
  ) {}

  async log(action: string, userId: string) {
    await this.logs.create({ action, userId, timestamp: new Date() });
  }

  async getRecent(limit: number) {
    return this.logs.findAll({ sort: { timestamp: -1 }, limit });
  }
}

Exemplo prático — Backend (Express)

❌ Usuário com interface monolítica

// Uma interface só para "usuário" — mas nem todo endpoint precisa de tudo
interface User {
  id: string;
  name: string;
  email: string;
  password: string;        // ❌ Rota pública nunca deveria ver isso
  cpf: string;             // ❌ Rota pública nunca deveria ver isso
  address: Address;        // ❌ Rota de login não precisa disso
  paymentMethods: Card[];  // ❌ Rota de perfil público não precisa disso
  role: 'admin' | 'user';
  permissions: string[];
}

// GET /api/users/public-profile — retorna User inteiro, incluindo password hash
app.get('/api/users/:id', async (req, res) => {
  const user = await db.users.findById(req.params.id);
  res.json(user); // ❌ expõe password, cpf, paymentMethods
});

✅ DTOs por contexto

// Cada rota vê só o que precisa
interface PublicUserProfile {
  id: string;
  name: string;
  role: string;
}

interface PrivateUserProfile extends PublicUserProfile {
  email: string;
  address: Address;
}

interface AuthenticatedUser extends PrivateUserProfile {
  permissions: string[];
}

// Rota pública — nunca vê password, cpf, paymentMethods
app.get('/api/users/:id', async (req, res) => {
  const user = await db.users.findById(req.params.id);
  const profile: PublicUserProfile = { id: user.id, name: user.name, role: user.role };
  res.json(profile); // ✅ só o que a rota pública precisa
});

// Rota de perfil privado — vê mais, mas não tudo
app.get('/api/users/:id/profile', authenticate, async (req, res) => {
  const user = await db.users.findById(req.params.id);
  const profile: PrivateUserProfile = pick(user, ['id', 'name', 'role', 'email', 'address']);
  res.json(profile);
});

Regra prática

  • Uma interface com mais de 5-6 métodos provavelmente viola ISP. Pense se pode ser dividida.
  • Implementações que têm métodos que só lançam Error('not supported') são sinal de ISP violado.
  • Pergunte: "Esse componente/serviço realmente USA todos os métodos que a interface exige?"

Referências

On this page