← Назад к вопросам

Как адаптер связан с Cohesion?

1.7 Middle🔥 142 комментариев
#JavaScript Core

Комментарии (2)

🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Как адаптер связан с Cohesion?

Это отличный вопрос о взаимосвязи паттерна Adapter (Адаптер) и Cohesion (Связанность). Хотя это разные концепции, они тесно связаны через принцип правильной архитектуры.

Что такое Adapter паттерн

Adapter - это структурный паттерн проектирования, который позволяет использовать несовместимые интерфейсы. Адаптер преобразует интерфейс одного класса/компонента в интерфейс, который ожидает другой класс.

Примеры:

// Старый API, который нельзя менять
class OldAPIResponse {
  getUserData() {
    return {
      name: 'John',
      age: 30,
      email_address: 'john@example.com'
    };
  }
}

// Новый API ожидает другой формат
interface User {
  fullName: string;
  age: number;
  email: string;
}

// Адаптер - преобразует старый формат в новый
function adaptOldToNew(oldData: ReturnType<OldAPIResponse['getUserData']>): User {
  return {
    fullName: oldData.name,
    age: oldData.age,
    email: oldData.email_address
  };
}

const oldAPI = new OldAPIResponse();
const user: User = adaptOldToNew(oldAPI.getUserData());

Что такое Cohesion

Cohesion (связанность) - это мера того, насколько сильно элементы компонента связаны между собой. Высокая связанность (high cohesion) означает, что элементы компонента имеют одну общую цель.

Низкая связанность (bad):

// Компонент отвечает за слишком много
function UserManager() {
  // Управление пользователями
  const [users, setUsers] = useState([]);
  
  // Форматирование дат (не связано с пользователями)
  const formatDate = (date) => new Date(date).toLocaleDateString();
  
  // Работа с платежами (совсем не связано)
  const processPayment = async (amount) => {
    // ...
  };
  
  // Обработка аналитики (не связано)
  const trackEvent = (event) => {
    // ...
  };
  
  return <div>User Manager</div>;
}

Высокая связанность (good):

// Компонент отвечает за одно - управление пользователями
function UserManager() {
  const [users, setUsers] = useState([]);
  
  const fetchUsers = async () => {
    const response = await fetch('/api/users');
    setUsers(await response.json());
  };
  
  const deleteUser = async (id) => {
    await fetch(`/api/users/${id}`, { method: 'DELETE' });
    setUsers(users.filter(u => u.id !== id));
  };
  
  useEffect(() => { fetchUsers(); }, []);
  
  return (
    <div>
      {users.map(user => (
        <UserCard key={user.id} user={user} onDelete={deleteUser} />
      ))}
    </div>
  );
}

Как Adapter улучшает Cohesion

Адаптер позволяет сохранять высокую связанность, изолируя несовместимые интерфейсы:

// API A возвращает данные в одном формате
const apiA = {
  getUser: async (id) => ({
    userId: id,
    userName: 'Alice',
    userAge: 25
  })
};

// API B возвращает в другом формате
const apiB = {
  fetchUser: async (id) => ({
    id,
    name: 'Bob',
    age: 30
  })
};

// Наш компонент ожидает единый интерфейс
interface User {
  id: string;
  name: string;
  age: number;
}

// Адаптеры для обеих API
const userAdapters = {
  adaptApiA: (data): User => ({
    id: data.userId,
    name: data.userName,
    age: data.userAge
  }),
  
  adaptApiB: (data): User => ({
    id: data.id,
    name: data.name,
    age: data.age
  })
};

// Компонент имеет одну ответственность - отображение пользователя
function UserProfile({ userId, apiVersion = 'A' }) {
  const [user, setUser] = useState<User | null>(null);
  
  useEffect(() => {
    const fetchUser = apiVersion === 'A' ? apiA.getUser : apiB.fetchUser;
    const adapter = apiVersion === 'A' 
      ? userAdapters.adaptApiA 
      : userAdapters.adaptApiB;
    
    fetchUser(userId).then(data => setUser(adapter(data)));
  }, [userId, apiVersion]);
  
  if (!user) return <div>Loading...</div>;
  
  return (
    <div>
      <h2>{user.name}</h2>
      <p>Age: {user.age}</p>
    </div>
  );
}

Adapter для Redux/состояния

Адаптеры часто используются для нормализации данных перед сохранением в Redux:

// API данные
interface APIProduct {
  product_id: number;
  product_name: string;
  product_price: number;
  in_stock: boolean;
}

// Redux состояние
interface Product {
  id: number;
  name: string;
  price: number;
  inStock: boolean;
}

// Адаптер
function adaptProductFromAPI(apiProduct: APIProduct): Product {
  return {
    id: apiProduct.product_id,
    name: apiProduct.product_name,
    price: apiProduct.product_price,
    inStock: apiProduct.in_stock
  };
}

// Reducer имеет одну ответственность - управлять состоянием
function productReducer(state = [], action) {
  switch (action.type) {
    case 'LOAD_PRODUCTS':
      // Преобразуем API данные в Redux формат через адаптер
      return action.payload.map(adaptProductFromAPI);
    default:
      return state;
  }
}

Adapter в интеграции третьих библиотек

// Третья библиотека DatePicker имеет свой формат
type LibraryDate = Date;

// Наше приложение использует другой формат
type AppDate = string; // 'YYYY-MM-DD'

// Адаптер для DatePicker
function DatePickerAdapter(props) {
  const [value, setValue] = useState<AppDate>('');
  
  const handleChange = (libraryDate: LibraryDate) => {
    // Преобразуем из формата библиотеки в формат приложения
    const appDate = libraryDate.toISOString().split('T')[0];
    setValue(appDate);
    props.onChange?.(appDate);
  };
  
  return (
    <DatePicker
      value={value ? new Date(value) : undefined}
      onChange={handleChange}
    />
  );
}

// Остальное приложение работает с единым форматом дат
function BookingForm() {
  const [date, setDate] = useState<AppDate>('');
  
  return (
    <DatePickerAdapter value={date} onChange={setDate} />
  );
}

Взаимосвязь Adapter и Cohesion

  1. Адаптер скрывает сложность - компонент остаётся сосредоточен на одной задаче

  2. Адаптер разделяет ответственность - трансформация данных отделена от логики

  3. Адаптер облегчает тестирование - можно тестировать адаптер отдельно от компонента

// Тест для адаптера (одна ответственность)
describe('User Adapter', () => {
  it('should adapt API response to app format', () => {
    const apiResponse = {
      userId: '123',
      userName: 'John',
      userAge: 30
    };
    
    const result = userAdapters.adaptApiA(apiResponse);
    
    expect(result).toEqual({
      id: '123',
      name: 'John',
      age: 30
    });
  });
});

// Тест для компонента (не заботится о деталях адаптации)
describe('UserProfile', () => {
  it('should display user data', () => {
    const user = { id: '1', name: 'John', age: 30 };
    // Тестируем компонент с уже адаптированными данными
    render(<UserProfile user={user} />);
    expect(screen.getByText('John')).toBeInTheDocument();
  });
});

Когда НЕ использовать Adapter

  • Если интерфейсы совместимы - адаптер добавляет ненужную сложность
  • Если адаптер используется только в одном месте - проще встроить трансформацию
  • Если данные меняются часто - мёртвый код

Вывод: Adapter паттерн и Cohesion связаны тем, что правильно использованный адаптер повышает связанность компонентов. Адаптер преобразует несовместимые интерфейсы, позволяя каждому компоненту сохранять одну чёткую ответственность. Это следствие принципа "Адаптер разделяет ответственность между компонентами".

Как адаптер связан с Cohesion? | PrepBro