Interface — договор, който дефинира набор от абстрактни методи, които един клас трябва да имплементира. В Java и Kotlin интерфейсите са основният механизъм за абстракция и полиморфизъм. В Java 8+ интерфейсите могат да съдържат default и static методи, в Kotlin — имплементации по подразбиране. Според Google Android Developers (2025), интерфейсите се използват в 90% от Android проектите за дефиниране на архитектурни слоеве — хранилища, UseCase и услуги.
Основни точки
Interface — референтен тип, който съдържа абстрактни методи, константи и методи по подразбиране. Класът, имплементиращ интерфейс, е длъжен да предостави имплементация на всички негови абстрактни методи. В Java интерфейсът не може да има състояние (полета на инстанция), Kotlin също спазва това ограничение, но поддържа свойства с аксесори.
public interface Repository {
T findById(Long id);
List findAll();
T save(T entity);
void deleteById(Long id);
default long count() {
return findAll().size();
}
}
@Entity
public class UserEntity {
private Long id;
private String email;
// getter-и и setter-и
}
public class UserRepositoryImpl implements Repository {
private final EntityManager em;
public UserEntity findById(Long id) {
return em.find(UserEntity.class, id);
}
public List findAll() {
return em.createQuery("FROM UserEntity", UserEntity.class)
.getResultList();
}
// останалите методи
}Repository<T> — generic интерфейс с CRUD методи. Методът по подразбиране count() предоставя имплементация по подразбиране, която може да бъде заменена. UserRepositoryImpl имплементира интерфейса, използвайки EntityManager за достъп до данни. Този подход позволява тестване на слоя за данни чрез mock на интерфейса без реална база данни.
Изборът между интерфейс и абстрактен клас зависи от наличието на общо състояние и връзки между типовете. Интерфейсът дефинира договор (какво може да прави класът), абстрактният клас — обща имплементация (какво е класът).
| Критерий | Interface | Abstract Class |
|---|---|---|
| Състояние (полета) | Само static final константи | Да, всякакви полета |
| Конструктори | Не | Да |
| Множествено наследяване | Да (implements) | Не (extends един) |
| Модификатори за достъп | public (Java 8-), default методи | Всички (private, protected, public) |
| Кога да използваме | Договор за несвързани класове | Обща основа за сродни класове |
В Clean Architecture интерфейсите се намират във вътрешния слой (domain), а имплементациите — във външния (data). Това позволява спазване на Правилото за зависимост: външният слой зависи от вътрешния, а не обратното. Абстрактните класове се използват по-често за шаблонни методи (Template Method pattern).
Kotlin интерфейсите са по-гъвкави от Java: могат да декларират абстрактни свойства и да предоставят имплементации на методи. За разлика от Java, Kotlin поддържа делегиране чрез ключовата дума by, което значително намалява boilerplate кода при имплементиране на модела Delegate.
interface ApiService {
val baseUrl: String // абстрактно свойство
suspend fun fetchData(): Result>
fun getEndpoint(path: String): String {
return "$baseUrl/$path" // имплементация по подразбиране
}
}
class RetrofitApiService(
override val baseUrl: String
) : ApiService {
private val client = Retrofit.Builder()
.baseUrl(baseUrl)
.build()
override suspend fun fetchData(): Result>
{
// имплементация на заявка
}
}
// Делегиране чрез by
interface Logger {
fun log(message: String)
}
class ConsoleLogger : Logger {
override fun log(message: String) = println(message)
}
class UserService(logger: Logger) : Logger by loggerUserService имплементира интерфейса Logger чрез делегиране (by). Всички извиквания на log() се пренасочват към обекта logger без писане на wrapper методи. Това е пример за композиция, която в Java би изисквала 5-10 реда boilerplate код.
Clean Architecture за Android ясно разделя приложението на слоеве. Интерфейсите играят ролята на граници между слоевете: Domain дефинира интерфейси на хранилища и UseCase, Data предоставя имплементации. Това позволява замяна на имплементации без промяна на бизнес логиката — ключово предимство при миграция от Room към Firebase или от REST към GraphQL.
// Domain слой — интерфейс (договор)
interface UserRepository {
suspend fun getUser(id: String): User
suspend fun updateUser(user: User)
}
// Domain слой — use case (зависи от интерфейса)
class GetUserUseCase(
private val repository: UserRepository
) {
suspend operator fun invoke(id: String): Result {
return runCatching { repository.getUser(id) }
}
}
// Data слой — имплементация
class UserRepositoryImpl(
private val api: UserApi,
private val dao: UserDao
) : UserRepository {
override suspend fun getUser(id: String): User {
val cached = dao.getUser(id)
if (cached != null) return cached
val remote = api.fetchUser(id)
dao.insertUser(remote)
return remote
}
// updateUser...
}UserRepository — интерфейс в domain слоя. GetUserUseCase зависи от интерфейса, а не от имплементацията. UserRepositoryImpl в data слоя имплементира интерфейса, комбинирайки API и локална база данни. Такава архитектура позволява тестване на UseCase с mock хранилище без конфигуриране на база данни или мрежа.
Default методите бяха добавени в Java 8 за еволюционно разширяване на интерфейсите без нарушаване на обратната съвместимост. Ако ArrayList не беше имплементирал новия метод stream(), добавен в Collection, старият код щеше да продължи да работи. Static методите в интерфейсите служат за помощни инструменти, свързани с интерфейса — алтернатива на помощните класове.
public interface Vehicle {
void start();
void stop();
default void honk() {
System.out.println("Beep!");
}
static Vehicle of(String type) {
if ("car".equals(type)) return new Car();
return new Bicycle();
}
// константа
String CATEGORY = "transport";
}Default методите решават диамантения проблем (diamond problem): ако клас имплементира два интерфейса с еднакъв default метод, компилаторът изисква изрично презаписване. Static методите се извикват чрез името на интерфейса — Vehicle.of("car"), без инстанция.
Грешките при проектиране на интерфейси водят до крехък код, трудности при тестване и нарушения на SOLID. Нека разгледаме три често срещани проблема.
Интерфейс с 15+ метода нарушава Interface Segregation Principle (ISP). Пример — старият java.util.Dictionary с 10+ метода. Решение: разделяне на няколко малки интерфейса — ReadableRepository, WritableRepository, SearchableRepository. Клиентът (услугата) зависи само от методите, от които се нуждае.
Създаването на интерфейс за всеки клас без реална нужда от полиморфизъм е анти-моделът Interface overkill. Признак: интерфейсът има точно една имплементация и в проекта няма планове за добавяне на алтернативи. Решение: добавяйте интерфейс само когато се появи втори вариант на имплементация или нужда от mock за тестове.
Често задавани въпроси
Interface дефинира само договора (сигнатури на методи), не може да има състояние и поддържа множествено наследяване. Abstract class може да съдържа полета, конструктори и имплементирани методи, но клас може да наследи само един абстрактен клас. От Java 8 интерфейсите получиха default и static методи, намалявайки разликата.
Да, в Java и Kotlin интерфейсите поддържат наследяване. public interface AdvancedRepository<T> extends Repository<T>, Pageable — интерфейс, обединяващ два други. Класът, имплементиращ AdvancedRepository, трябва да имплементира всички методи и на двата родителски интерфейса. Множественото наследяване е разрешено само за интерфейси.
Функционален интерфейс — интерфейс с един абстрактен метод (SAM — Single Abstract Method). Анотацията @FunctionalInterface гарантира това ограничение. Примери: Runnable, Callable, Comparator, Consumer. Функционалните интерфейси са основата на ламбда изразите в Java 8: () -> System.out.println() имплементира Runnable.
Default методите позволяват добавяне на нови методи в интерфейс без промяна на всички имплементиращи класове. Например Java 8 добави stream() в Collection като default метод. Без този механизъм foreach(), stream() и други методи биха изисквали промяна на хиляди класове в JDK. Default — обратно съвместим начин за разширяване.
Kotlin забранява множественото наследяване на класове, но разрешава множествена имплементация на интерфейси. Ако два интерфейса имат метод с еднаква сигнатура и имплементация по подразбиране, компилаторът изисква изрично презаписване с извикване на super<InterfaceName>.method(). Това решава диамантения проблем на ниво компилация.
Обобщение
Ще разработим мобилно приложение под ключ
IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също