Interface — este un contract care definește un set de metode abstracte pe care o clasă trebuie să le implementeze. În Java și Kotlin, interfețele sunt mecanismul principal de abstractizare și polimorfism. În Java 8+ interfețele pot conține metode default și static, iar în Kotlin — implementări implicite. Conform Google Android Developers (2025), interfețele sunt utilizate în 90% din proiectele Android pentru definirea straturilor arhitecturii — repository-uri, UseCase și servicii.
Principalele
Interface — este un tip de referință care conține metode abstracte, constante și metode implicite. Clasa care implementează interfața este obligată să furnizeze implementarea tuturor metodelor sale abstracte. În Java, interfața nu poate avea stare (câmpuri de instanță), Kotlin respectă și el această restricție, dar suportă proprietăți cu accesori.
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;
// gettere și settere
}
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();
}
// celelalte metode
}Repository<T> — o interfață generică cu metode CRUD. Metoda implicită count() oferă o implementare implicită care poate fi suprascrisă. UserRepositoryImpl implementează interfața, folosind EntityManager pentru accesul la date. Această abordare permite testarea stratului de date printr-un mock al interfeței fără o bază de date reală.
Alegerea între interfață și clasa abstractă depinde de existența stării comune și a relațiilor dintre tipuri. Interfața definește un contract (ce poate face clasa), clasa abstractă — o implementare comună (ce este clasa).
| Criteriu | Interface | Abstract Class |
|---|---|---|
| Stare (câmpuri) | Doar constante static final | Da, orice câmpuri |
| Constructori | Nu | Da |
| Moștenire multiplă | Da (implements) | Nu (extends unul) |
| Modificatori de acces | public (Java 8-), metode default | Toate (private, protected, public) |
| Când să folosești | Contract pentru clase neînrudite | Bază comună pentru clase înrudite |
În Clean Architecture interfețele sunt plasate în stratul interior (domain), iar implementările — în stratul exterior (data). Aceasta permite respectarea Regulii Dependenței: stratul exterior depinde de cel interior, dar nu invers. Clasele abstracte sunt folosite mai des pentru metode șablon (Template Method pattern).
Interfețele Kotlin sunt mai flexible decât în Java: pot declara proprietăți abstracte și pot oferi implementări de metode. Spre deosebire de Java, Kotlin suportă delegarea prin cuvântul cheie by, ceea ce reduce semnificativ boilerplate-ul la implementarea pattern-ului Delegate.
interface ApiService {
val baseUrl: String // proprietate abstractă
suspend fun fetchData(): Result>
fun getEndpoint(path: String): String {
return "$baseUrl/$path" // implementare implicită
}
}
class RetrofitApiService(
override val baseUrl: String
) : ApiService {
private val client = Retrofit.Builder()
.baseUrl(baseUrl)
.build()
override suspend fun fetchData(): Result>
{
// implementarea cererii
}
}
// Delegare prin by
interface Logger {
fun log(message: String)
}
class ConsoleLogger : Logger {
override fun log(message: String) = println(message)
}
class UserService(logger: Logger) : Logger by loggerUserService implementează interfața Logger prin delegare (by). Toate apelurile log() sunt redirecționate către obiectul logger fără a scrie metode wrapper. Acesta este un exemplu de compoziție care în Java ar necesita 5-10 linii de cod boilerplate.
Clean Architecture pentru Android separă clar aplicația în straturi. Interfețele joacă rolul de granițe între straturi: Domain definește interfețele repository-urilor și UseCase-urilor, Data oferă implementări. Aceasta permite înlocuirea implementărilor fără a modifica logica de business — un avantaj cheie la migrarea de la Room la Firebase sau de la REST la GraphQL.
// Stratul Domain — interfață (contract)
interface UserRepository {
suspend fun getUser(id: String): User
suspend fun updateUser(user: User)
}
// Stratul Domain — use case (depinde de interfață)
class GetUserUseCase(
private val repository: UserRepository
) {
suspend operator fun invoke(id: String): Result {
return runCatching { repository.getUser(id) }
}
}
// Stratul Data — implementare
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 — interfață în stratul domain. GetUserUseCase depinde de interfață, nu de implementare. UserRepositoryImpl în stratul data implementează interfața, combinând API și baza de date locală. O astfel de arhitectură permite testarea UseCase-urilor cu un repository mock fără configurarea bazei de date sau a rețelei.
Metodele default au fost adăugate în Java 8 pentru extinderea evolutivă a interfețelor fără a încălca compatibilitatea inversă. Dacă ArrayList nu ar fi implementat noua metodă stream() adăugată în Collection, codul vechi ar fi continuat să funcționeze. Metodele static în interfețe servesc pentru utilități legate de interfață — o alternativă la clasele utilitare.
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();
}
// constantă
String CATEGORY = "transport";
}Metodele default rezolvă problema diamantului (diamond problem): dacă o clasă implementează două interfețe cu aceeași metodă default, compilatorul solicită suprascrierea explicită. Metodele static sunt apelate prin numele interfeței — Vehicle.of("car"), fără instanță.
Greșelile în proiectarea interfețelor duc la cod fragil, dificultăți de testare și încălcări ale SOLID. Să analizăm trei probleme frecvente.
O interfață cu 15+ metode încalcă Interface Segregation Principle (ISP). Exemplu — vechiul java.util.Dictionary cu 10+ metode. Soluție: împărțirea în mai multe interfețe mici — ReadableRepository, WritableRepository, SearchableRepository. Clientul (serviciul) depinde doar de metodele de care are nevoie.
Crearea unei interfețe pentru fiecare clasă fără nevoia reală de polimorfism este anti-pattern-ul Interface overkill. Semn: interfața are exact o implementare și nu există planuri de adăugare a alternativelor în proiect. Soluție: adăugați interfață doar când apare a doua variantă de implementare sau nevoia de mock pentru teste.
Întrebări frecvente
Interface definește doar contractul (semnăturile metodelor), nu poate avea stare și suportă moștenirea multiplă. Abstract class poate conține câmpuri, constructori și metode implementate, dar o clasă poate moșteni o singură clasă abstractă. Începând cu Java 8, interfețele au primit metode default și static, reducând diferența.
Da, în Java și Kotlin interfețele suportă moștenirea. public interface AdvancedRepository<T> extends Repository<T>, Pageable — o interfață care combină alte două. Clasa care implementează AdvancedRepository trebuie să implementeze toate metodele ambelor interfețe părinte. Moștenirea multiplă este permisă doar pentru interfețe.
Interfața funcțională — o interfață cu o singură metodă abstractă (SAM — Single Abstract Method). Adnotarea @FunctionalInterface garantează această restricție. Exemple: Runnable, Callable, Comparator, Consumer. Interfețele funcționale stau la baza expresiilor lambda din Java 8: () -> System.out.println() implementează Runnable.
Metodele default permit adăugarea de noi metode într-o interfață fără a modifica toate clasele care o implementează. De exemplu, Java 8 a adăugat stream() în Collection ca metodă default. Fără acest mecanism, foreach(), stream() și alte metode ar fi necesitat modificarea a mii de clase în JDK. Default — o modalitate compatibilă invers de extindere.
Kotlin interzice moștenirea multiplă a claselor, dar permite implementarea multiplă a interfețelor. Dacă două interfețe au o metodă cu aceeași semnătură și implementare implicită, compilatorul solicită suprascrierea explicită cu apelul super<InterfaceName>.method(). Aceasta rezolvă problema diamantului la nivel de compilare.
Concluzii
Vom dezvolta o aplicație mobilă la cheie
IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.
Citiți și