Interface คือสัญญาที่กำหนดชุดของเมธอดนามธรรมที่คลาสต้อง implement ใน Java และ Kotlin อินเทอร์เฟซเป็นกลไกหลักของนามธรรมและพหุนาม ใน Java 8+ อินเทอร์เฟซสามารถมีเมธอด default และ static ได้ ใน Kotlin — การ implement เริ่มต้น ตามข้อมูลจาก Google Android Developers (2025) อินเทอร์เฟซถูกใช้ใน 90% ของโปรเจกต์ Android เพื่อกำหนดเลเยอร์สถาปัตยกรรม — repository, UseCase และบริการ
ประเด็นสำคัญ
Interface คือชนิดอ้างอิงที่ประกอบด้วยเมธอดนามธรรม ค่าคงที่ และเมธอดเริ่มต้น คลาสที่ implement อินเทอร์เฟซต้องให้ implement สำหรับเมธอดนามธรรมทั้งหมด ใน Java อินเทอร์เฟซไม่สามารถมีสถานะ (ฟิลด์ instance) ได้ 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> คืออินเทอร์เฟซทั่วไปที่มีเมธอด CRUD เมธอด default count() ให้ implement เริ่มต้นที่สามารถแทนที่ได้ UserRepositoryImpl implement อินเทอร์เฟซโดยใช้ EntityManager สำหรับการเข้าถึงข้อมูล วิธีนี้ช่วยให้ทดสอบเลเยอร์ข้อมูลผ่าน mock ของอินเทอร์เฟซโดยไม่ต้องมีฐานข้อมูลจริง
การเลือกระหว่างอินเทอร์เฟซและคลาสนามธรรม ขึ้นอยู่กับการมีสถานะร่วมและความสัมพันธ์ระหว่างชนิด อินเทอร์เฟซกำหนดสัญญา (สิ่งที่คลาสทำได้) คลาสนามธรรมกำหนด implement ร่วม (สิ่งที่คลาสเป็น)
| เกณฑ์ | Interface | Abstract Class |
|---|---|---|
| สถานะ (ฟิลด์) | เฉพาะค่าคงที่ static final | ใช่ ฟิลด์ใดก็ได้ |
| คอนสตรัคเตอร์ | ไม่มี | มี |
| การสืบทอดหลายทาง | ได้ (implements) | ไม่ได้ (extends หนึ่ง) |
| ตัวปรับการเข้าถึง | public (Java 8-), เมธอด default | ทั้งหมด (private, protected, public) |
| เมื่อไรใช้ | สัญญาสำหรับคลาสที่ไม่เกี่ยวข้อง | ฐานร่วมสำหรับคลาสที่เกี่ยวข้อง |
ใน Clean Architecture อินเทอร์เฟซถูกวางในเลเยอร์ด้านใน (domain) และ implement ในเลเยอร์ด้านนอก (data) ซึ่งช่วยให้คงกฎการพึ่งพา (Dependency Rule): เลเยอร์ด้านนอกขึ้นอยู่กับเลเยอร์ด้านใน แต่ไม่ใช่ในทางกลับกัน คลาสนามธรรมมักใช้สำหรับเมธอดแม่แบบ (Template Method pattern)
อินเทอร์เฟซ Kotlin ยืดหยุ่นกว่า Java: สามารถประกาศคุณสมบัตินามธรรมและให้ implement เมธอดได้ แตกต่างจาก Java Kotlin รองรับการมอบหมายผ่านคำสำคัญ by ซึ่งช่วยลดโค้ดสำเร็จรูปอย่างมากเมื่อ implement รูปแบบ Delegate
interface ApiService {
val baseUrl: String // คุณสมบัตินามธรรม
suspend fun fetchData(): Result>
fun getEndpoint(path: String): String {
return "$baseUrl/$path" // implement เริ่มต้น
}
}
class RetrofitApiService(
override val baseUrl: String
) : ApiService {
private val client = Retrofit.Builder()
.baseUrl(baseUrl)
.build()
override suspend fun fetchData(): Result>
{
// การ implement คำขอ
}
}
// การมอบหมายผ่าน by
interface Logger {
fun log(message: String)
}
class ConsoleLogger : Logger {
override fun log(message: String) = println(message)
}
class UserService(logger: Logger) : Logger by loggerUserService implement อินเทอร์เฟซ Logger ผ่านการมอบหมาย (by) การเรียก log() ทั้งหมดถูกส่งต่อไปยังออบเจกต์ logger โดยไม่ต้องเขียนเมธอดหุ้ม นี้คือตัวอย่างของการประกอบที่ใน Java ต้องใช้โค้ดสำเร็จรูป 5-10 บรรทัด
Clean Architecture สำหรับ Android แบ่งแอปพลิเคชันออกเป็นเลเยอร์อย่างชัดเจน อินเทอร์เฟซทำหน้าที่เป็นขอบเขตระหว่างเลเยอร์: Domain กำหนดอินเทอร์เฟซ repository และ UseCase Data ให้ implement ซึ่งช่วยให้เปลี่ยน implement ได้โดยไม่ต้องเปลี่ยนตรรกะทางธุรกิจ — ข้อได้เปรียบสำคัญเมื่อย้ายจาก Room ไป Firebase หรือจาก REST ไป GraphQL
// Domain layer — อินเทอร์เฟซ (สัญญา)
interface UserRepository {
suspend fun getUser(id: String): User
suspend fun updateUser(user: User)
}
// Domain layer — use case (ขึ้นอยู่กับอินเทอร์เฟซ)
class GetUserUseCase(
private val repository: UserRepository
) {
suspend operator fun invoke(id: String): Result {
return runCatching { repository.getUser(id) }
}
}
// Data layer — implement
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 ขึ้นอยู่กับอินเทอร์เฟซ ไม่ใช่ implement UserRepositoryImpl ในเลเยอร์ data implement อินเทอร์เฟซ โดยรวม API และฐานข้อมูลท้องถิ่น สถาปัตยกรรมนี้ช่วยให้ทดสอบ UseCases กับ repository mock โดยไม่ต้องตั้งค่าฐานข้อมูลหรือเครือข่าย
เมธอด default ถูกเพิ่มใน Java 8 สำหรับการขยายอินเทอร์เฟซแบบวิวัฒนาการโดยไม่ทำลายความเข้ากันได้ย้อนหลัง ถ้า ArrayList ไม่ได้ implement เมธอด 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): ถ้าคลาส implement อินเทอร์เฟซสองตัวที่มีเมธอด default เดียวกัน คอมไพเลอร์ต้องการการแทนที่อย่างชัดแจ้ง เมธอด static ถูกเรียกผ่านชื่ออินเทอร์เฟซ — Vehicle.of("car") โดยไม่มี instance
ข้อผิดพลาดในการออกแบบอินเทอร์เฟซ นำไปสู่โค้ดที่เปราะบาง ความซับซ้อนในการทดสอบ และการละเมิด SOLID มาดูปัญหาทั่วไปสามข้อ
อินเทอร์เฟซที่มี 15+ เมธอดละเมิด Interface Segregation Principle (ISP) ตัวอย่างคือ java.util.Dictionary เก่าที่มี 10+ เมธอด วิธีแก้: แบ่งเป็นอินเทอร์เฟซเล็กหลายอัน — ReadableRepository, WritableRepository, SearchableRepository ไคลเอนต์ (บริการ) ขึ้นอยู่กับเมธอดที่ต้องการเท่านั้น
การสร้างอินเทอร์เฟซสำหรับทุกคลาสโดยไม่มีความต้องการพหุนามที่แท้จริงคือปฏิยานุกรม Interface overkill ตัวบ่งชี้: อินเทอร์เฟซมี implement เพียงหนึ่งเดียวและโปรเจกต์ไม่มีแผนเพิ่มทางเลือก วิธีแก้: เพิ่มอินเทอร์เฟซเมื่อมีตัวเลือก implement ที่สองปรากฏขึ้นหรือต้องการ mock สำหรับการทดสอบ
คำถามที่พบบ่อย
Interface กำหนดเพียงสัญญา (ลายเซ็นเมธอด) ไม่สามารถมีสถานะและรองรับการสืบทอดหลายทาง Abstract class สามารถมีฟิลด์ คอนสตรัคเตอร์ และเมธอดที่ implement แล้ว แต่คลาสสามารถสืบทอดคลาสนามธรรมได้เพียงหนึ่งเดียว ตั้งแต่ Java 8 เป็นต้นมา อินเทอร์เฟซได้รับเมธอด default และ static ทำให้ช่องว่างแคบลง
ใช่ ใน Java และ Kotlin อินเทอร์เฟซรองรับการสืบทอด public interface AdvancedRepository<T> extends Repository<T>, Pageable คืออินเทอร์เฟซที่รวมอีกสองอัน คลาสที่ implement AdvancedRepository ต้อง implement เมธอดทั้งหมดของอินเทอร์เฟซหลักทั้งสอง การสืบทอดหลายทางอนุญาตเฉพาะสำหรับอินเทอร์เฟซ
อินเทอร์เฟซฟังก์ชัน คืออินเทอร์เฟซที่มีเมธอดนามธรรมเดี่ยว (SAM — Single Abstract Method) คำอธิบายประกอบ @FunctionalInterface รับประกันข้อจำกัดนี้ ตัวอย่าง: Runnable, Callable, Comparator, Consumer อินเทอร์เฟซฟังก์ชันเป็นพื้นฐานของนิพจน์ lambda ใน Java 8: () -> System.out.println() implement Runnable
เมธอด default ช่วยให้เพิ่มเมธอดใหม่ในอินเทอร์เฟซโดยไม่ต้องแก้ไขคลาสที่ implement ทั้งหมด ตัวอย่างเช่น Java 8 เพิ่ม stream() ใน Collection เป็นเมธอด default หากไม่มีกลไกนี้ foreach(), stream() และเมธอดอื่น ๆ จะต้องเปลี่ยนแปลงคลาสนับพันใน JDK Default เป็นวิธีขยายที่เข้ากันได้ย้อนหลัง
Kotlin ห้ามการสืบทอดหลายทางของคลาสแต่อนุญาตให้ implement อินเทอร์เฟซหลายตัว ถ้าอินเทอร์เฟซสองตัวมีเมธอดที่มีลายเซ็นและ implement เริ่มต้นเดียวกัน คอมไพเลอร์ต้องการการแทนที่อย่างชัดแจ้งด้วยการเรียก super<InterfaceName>.method() ซึ่งแก้ปัญหาเพชรในระดับการคอมไพล์
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ
อ่านเพิ่มเติม