Protocol-oriented programming: Trái tim của Swift!

Công Nghệ
Protocol-oriented programming: Trái tim của Swift!
Bài viết được sự cho phép của tác giả Lê Xuân Quỳnh Nếu bạn đang code Swift và vẫn không biết gì về Protocol-oriented programming(POP) thì đúng là bạn chưa biết gì về Swift. Nếu bạn vẫn giữ khư khư quan điểm lập trình hướng đối tượng(object-oriented paradigm) trong swift và không chịu tiếp nhận POP, thì thật là buồn khi tôi nói bạn không nên tiếp tục đọc bài viết này. Tất nhiên điều đó cũng làm tôi buồn lắm, vì bạn đã bỏ lỡ 1 bài viết quan trọng Các cách sử dụng AS, AS?, AS! một cách hiệu quả và an toàn trong code Swift Các ưu nhược điểm của Swift so với Objective C Xem thêm nhiều việc làm Swift hấp dẫn trên Station D Nếu bạn vẫn ở lại, hãy cùng xem những ví dụ đơn giản của POP để thấy được sức mạnh của nó mang lại nhé. Bài viết tiếng Anh vui lòng xem tại: Lập trình hướng đối tượng(OOP) là 1 trong những mô hình mà đa số developer sẽ biết đến đầu tiên, là 1 cách tiếp cận phổ biến nhất ở đại đa số các ngôn ngữ lập trình. Tuy nhiên, nó không phải là tất cả. Trong những năm gần đây, cách tiếp cận POP được cộng đồng Swift sử dụng rất nhiều. Nó không phải là thứ gì đó mới và sáng bóng, cũng không phải là viên đạn bạc cho mọi vấn đề. Tuy nhiên, nó đóng vai trò hữu ích trong việc cấu trúc source code. Trong bài viết này, chúng ta cùng nhau đi qua các ví dụ để thấy sự hữu ích của nó. Object-oriented approach trong Swift Hãy tưởng tượng bạn được yêu cầu viết 1 chương trình trò chơi, chỉ với 2...

Bài viết được sự cho phép của tác giả Lê Xuân Quỳnh

Nếu bạn đang code Swift và vẫn không biết gì về Protocol-oriented programming(POP) thì đúng là bạn chưa biết gì về Swift. Nếu bạn vẫn giữ khư khư quan điểm lập trình hướng đối tượng(object-oriented paradigm) trong swift và không chịu tiếp nhận POP, thì thật là buồn khi tôi nói bạn không nên tiếp tục đọc bài viết này. Tất nhiên điều đó cũng làm tôi buồn lắm, vì bạn đã bỏ lỡ 1 bài viết quan trọng 😭😭

Xem thêm nhiều việc làm Swift hấp dẫn trên Station D

Nếu bạn vẫn ở lại, hãy cùng xem những ví dụ đơn giản của POP để thấy được sức mạnh của nó mang lại nhé.

Bài viết tiếng Anh vui lòng xem tại:

Lập trình hướng đối tượng(OOP) là 1 trong những mô hình mà đa số developer sẽ biết đến đầu tiên, là 1 cách tiếp cận phổ biến nhất ở đại đa số các ngôn ngữ lập trình. Tuy nhiên, nó không phải là tất cả. Trong những năm gần đây, cách tiếp cận POP được cộng đồng Swift sử dụng rất nhiều. Nó không phải là thứ gì đó mới và sáng bóng, cũng không phải là viên đạn bạc cho mọi vấn đề. Tuy nhiên, nó đóng vai trò hữu ích trong việc cấu trúc source code. Trong bài viết này, chúng ta cùng nhau đi qua các ví dụ để thấy sự hữu ích của nó.

Object-oriented approach trong Swift

Hãy tưởng tượng bạn được yêu cầu viết 1 chương trình trò chơi, chỉ với 2 level: Cấp độ 1 là trên mặt đất, cấp độ 2 là dưới lòng đất.

Trước tiên hãy dùng hướng đối tượng – OOP để viết. Chúng ta cần liệt kê những điểm chung nhất để bố trí các lớp. Chúng ta cũng sẽ cần 2 loại nhân vật: một cho người chơi, một cho các sinh vật kẻ thù trên đất liền và một đại diện cho quái vật địa ngục. Hãy bắt đầu với class như sau:

Creature

Các thuộc tính và hành vi chung được mô tả như sau:

class Creature {
    let name: String
    init(name: String) {
        self.name = name
    }

    func fight() {
        print("👊")
    }
}

Kẻ thù trên mặt đất có thể chạy bộ và chiến đấu với người chơi. Lớp sinh vật mặt đất được mô tả như sau:

class LandCreature: Creature {
    func walk() {
        print("🚶🏻‍♀️")
    }

    func run() {
        print("🏃🏻")
    }
}
A screenshot with a fragment of a code (landcreature)A screenshot with a fragment of a code (landcreature)

Còn những con quái vật trong lòng đất rất nguy hiểm, nó có thể thiêu cháy mọi thứ nó gặp. Chúng nó cũng có thể đi bộ và chạy tới nạn nhân, sau đó thiêu đốt nạn nhân:

class HellCreature: Creature {
    func walk() {
        print("🚶🏻‍♀️")
    }
    func run() {
        print("🏃🏻")
    }
    func burn() {
        print("🔥")
    }
}

Tại thời điểm này, chúng ta có thể sẽ quyết định rằng chạy(run) và đi bộ(walk) là khả năng cơ bản của tất cả các nhân vật, vì vậy chúng tôi nên chuyển chúng vào lớp cha:

class Creature {
    let name: String
    init(name: String) {
        self.name = name
    }

    func walk() {
        print("🚶🏻‍♀️")
    }
    func run() {
        print("🏃🏻")
    }
    func fight() {
        print("👊")
    }
}
class LandCreature: Creature { }
class HellCreature: Creature {
    func burn() {
        print("🔥")
    }
}

Sau đó chúng ta hãy tìm hiểu về HellCreature – một loại sinh vật có thể chạy, chiến đấu và thổi lửa:

A screenshot with a fragment of a code (hellcreature)A screenshot with a fragment of a code (hellcreature)

Vậy là xong, chúng ta đã hoàn thành để chơi.

Tại thời điểm này, sếp của bạn có thể sẽ nảy ra một ý tưởng tuyệt vời về việc thêm cấp độ cao cấp hơn vào trò chơi. Hãy thêm 1 loại sinh vật bay nổi loạn(pilot) vào trò chơi:

class SkyCreature: Creature {
    func fly() {
        print("🕊️")
    }
}

Hãy khởi tạo loài này như sau:

A screenshot with a fragment of a code (skycreature)A screenshot with a fragment of a code (skycreature)

Ồ, điều ngạc nhiên là loài sinh vật này lại có thể chạy bộ. Điều quái quỷ gì thế này? Hãy sửa nó bằng cách viết lại 2 phương thức run() và walk() và thêm dòng này vào:

fatalError()

để nó phát sinh lỗi khi cố tình gọi. Hoặc chúng ta có thể di chuyển chúng về lớp LandCreature và HellCreature. Dù sao thì nó cũng là phần cuối của dự án. Một sự trùng lặp mã nhỏ cũng chả chết ai, phải không?

class Creature {
    let name: String
    init(name: String) {
        self.name = name
    }

    func fight() {
        print("👊")
    }
}
class LandCreature: Creature {
    func walk() {
        print("🚶🏻‍♀️")
    }

    func run() {
        print("🏃🏻")
    }
}
class HellCreature: Creature {
    func walk() {
        print("🚶🏻‍♀️")
    }
    func run() {
        print("🏃🏻")
    }
    func burn() {
        print("🔥")
    }
}

Cuối cùng, Kanimoor không thể chạy hoặc đi bộ được nữa.

A screenshot with a fragment of a code (skycreature)A screenshot with a fragment of a code (skycreature)

Một lần nữa, trò chơi đã hoàn thành. Chà… gần như vậy. Trong phiên bản chào năm mới của trò chơi, sẽ có thêm một cấp độ nữa để chơi… đó là đấu với RỒNG! Tôi nghĩ bạn đã có thể nhìn thấy những rắc rối mà chúng ta sẽ gặp phải…

class Dragon: SkyCreature { }
A screenshot with a fragment of a code (dragon)A screenshot with a fragment of a code (dragon)

Tất nhiên 1 con Wyvern thì có thể đi bộ, chạy và phun lửa. Một lần nữa chúng ta có thể di chuyển walk() và run() vào lớp cha, hoặc sao chép code và dán vào lớp Dragon này.

Lập trình hướng giao thức – Protocol-oriented programming

Lần này, giả sử chúng ta có cơ hội bắt đầu lại toàn bộ dự án (đó là một viễn cảnh khá đẹp, phải không?) Và hãy xem cách chúng ta có thể sử dụng các giao thức để cấu trúc toàn bộ vũ trụ tốt hơn. Để làm được điều đó, trước tiên chúng ta phải tìm hiểu lập trình hướng giao thức(POP) với Swift là gì và nó cung cấp những gì. WWDC15 Apple nói rằng:

Protocols have all these advantages, and that’s why, when we made Swift, we made the first protocol-oriented programming language. So, yes, Swift is great for object-oriented programming, but from the way for loops and string literals work to the emphasis in the standard library on generics, at its heart, Swift is protocol-oriented.

protocol defines a blueprint of methods, properties, and other requirements that suit a particular task or piece of functionality

…Một giao thức(protocol) xác định một bản thiết kế các phương thức, thuộc tính và các yêu cầu khác phù hợp với một nhiệm vụ hoặc một phần chức năng cụ thể. 

Protocols tương tự như interfaces -lớp giao diện ở các ngôn ngữ khác. Tuy nhiên với swift thì nó độc đáo và tiện ích hơn nhiều. Đầu tiên, ta có thể có một triển khai protocol mặc định và bắt buộc. Ví dụ:

protocol Running {
    func run()
}

extension Running {
    func run() {
        print("🏃🏻")
    }
}

Kể từ bây giờ loại protocol Running sẽ implementation phương thức run(). Tất nhiên, đôi khi bạn có thể muốn ghi đè nó bằng một cách triển khai hàm run() theo cách khác. Còn khi không muốn, giá trị mặc định phải đủ và hữu ích trong việc tránh trùng lặp mã. Các giá trị mặc định có thể cung cấp cho các lớp chỉ định. Trong ví dụ sau những loại có Persevering và Walking , giao thức Persevering sẽ triển khai phương thức stepByStep() như sau:

extension Persevering where Self: Walking {
    
    func stepByStep() {
        walk()
        walk()
    }
}
Screenshot with a fragment of a code (woolf).Screenshot with a fragment of a code (woolf).

Các kiểu khác nhau có thể áp dụng nhiều giao thức khác nhau, vì chúng có thể làm nhiều việc. Đồng thời, chúng chỉ có thể là một thứ (chỉ kế thừa một lớp cha). Một điều rất quan trọng khác là thực tế là các protocols có thể được chấp nhận bởi cả kiểu tham chiếu (Class) và kiểu tham trị (Struct và Enum), trong khi các lớp cha và con kế thừa bị giới hạn ở các kiểu tham chiếu. Chúng ta không đề cập đến sự khác biệt giữa tham trị và tham chiếu ở đây, nhưng tôi thực sự khuyên bạn nên tìm hiểu điều này. Đó là một tính năng chính khác trong Swift thực sự đáng để biết và sử dụng một cách khôn ngoan.

Extensions

Chúng ta hãy lập mô hình cấu trúc của một ứng dụng trước, thay vì buộc phải đưa ra tất cả các quyết định từ trước. Với các tiếp cận POP, chúng ta có thể bắt đầu triển khai hai cấp độ đầu tiên của trò chơi. Đầu tiên hãy xem xét lớp cơ sở Creature. Chúng ta hãy cho phép phương thức chiến đấu fight() qua protocol Strikeable:

class Creature {
    let name: String
    init(name: String) {
        self.name = name
    }
}

protocol Strikeable {
    func fight()
}

extension Strikeable {
    func fight() {
        print("👊")
    }
}

extension Creature: Strikeable { }

Định nghĩa hàm fight() riêng biệt trong protocol Strikeable, cung cấp khả năng tấn công cho Creatures. Cấp độ đầu tiên diễn ra trên mặt đất, nơi người chơi sẽ chiến đấu chống lại:

LandCreatures

Hành động cơ bản của chúng (ngoài chiến đấu) là đi bộ và chạy. Hai giao thức như sau:

protocol Walking {
    func walk()
}

extension Walking {
    func walk() {
        print("🚶🏻‍♀️")
    }
}
protocol Running {
    func run()
}

extension Running {
    func run() {
        print("🏃🏻")
    }
}

Với 2 protocols trên, LandCreature sẽ được thiết kế khá đơn giản như sau:

class LandCreature: Creature,
                    Walking,
                    Running { }

Các hành động của con Woolfie từ lớp LandCreature có thể được mô tả như sau:

A screenshot with a fragment of a code (landcreature – woolfie).A screenshot with a fragment of a code (landcreature – woolfie).

Hãy xuống địa ngục một lần nữa (?)! Bây giờ hãy thiết kế lớp cho loài HellCreature. Chúng ta sẽ thiết kế hành động đốt cháy burn() trong protocol Burning:

protocol Burning {
    func burn()
}

extension Burning {
    func burn() {
       print("🔥")
    }
}
Và đơn giản, HellCreature được thiết kế như sau:
class HellCreature: Creature,
                    Walking,
                    Running,
                    Burning { }
A screenshot with a fragment of a code (hellcreature)A screenshot with a fragment of a code (hellcreature)

Thật đơn giản phải không. Bây giờ hãy thiết kế cấp độ 3 cho trò chơi có tên là “Cuộc nổi dậy trên bầu trời” sẽ yêu cầu thêm các hành động fly() và fight() cho loài SkyCreature. Chúng ta cần sửa phương thức fight() mặc định để nó chiến đấu trên bầu trời theo cách của nó:

protocol Flying {
    func fly()
}

extension Flying {
    func fly() {
        print("🛩️")
    }
}
class SkyCreature: Creature, Flying {
    func fight() { 
        print(„🏹")
    }
}
A screenshot with a fragment of a code (skycreature)A screenshot with a fragment of a code (skycreature)

Thật là đơn giản và nhanh chóng phải không? Bây giờ hãy mô tả lớp Rồng như sau:

class Dragon: Creature,
              Walking,
              Running,
              Flying,
              Burning { }
A screenshot with a fragment of a code (dragon)A screenshot with a fragment of a code (dragon)

Thật là một thành công lớn! Quá trình tạo các lớp theo hướng giao thức của chúng ta rất dễ dàng!

Bằng cách sử dụng protocol, chúng ta có thể mô tả tất cả các loại sinh vật 1 cách đơn giản, mà không cần qua cách tiếp cận hướng đối tượng phân cấp giữa các lớp cứng nhắc. Với các protocol, chúng ta có thể thêm các thuộc tính thuận tiện để kiểm tra xem một đối tượng nhất định có thể thực hiện một tác vụ cụ thể hay không:

extension Creature {
    
    var canFly: Bool { return self is Flying }
    var canBurn: Bool { return self is Burning }
    var canWalk: Bool { return self is Walking }
}

Điều tuyệt vời ở đây là bạn không cần phải cập nhật các giá trị này khi bất kỳ giá trị nào trong số chúng ngừng tuân thủ giao thức. Đây là các thuộc tính computed properties, vì vậy kết quả sẽ tự động thay đổi.

Rủi ro trong lập trình hướng giao thức POP

Cũng giống như lập trình hướng đối tượng có nguy cơ tạo ra một hệ thống phân cấp lớp rất phức tạp, lập trình hướng giao thức có thể khiến cấu trúc phát triển quá nhiều theo chiều ngang. Quá nhiều mở rộng do tạo ra nhiều giao thức nhỏ sẽ khiến việc duy trì và sử dụng ứng dụng trở nên khó khăn và phiền phức. Chắc chắn, protocols cũng yêu cầu phân lớp trong dự án để bạn không bị mất dấu về những protocol nào đã được thêm vào những class nào. Nó giống như bất kỳ kỹ thuật nào khác: cần phải tìm ra sự cân bằng khi chọn lựa giữa OOP và POP để giải quyết vấn đề chứ không phải là cứng nhắc khi chọn lựa.

Kết luận

Trong lập trình hướng đối tượng, chúng ta tập trung vào đối tượng là gì, trong khi phương pháp hướng giao thức cho phép chúng ta tập trung nhiều hơn vào những gì một đối tượng có thể làm, khả năng và hành vi của nó. Ví dụ trò chơi đơn giản của chúng ta nhằm nhấn mạnh sự khác biệt trong quá trình phát triển bằng cách sử dụng cả hai mô hình này. Lúc đầu, phần mở rộng giao thức(extension) và triển khai mặc định có vẻ giống với các lớp cơ sở hoặc lớp trừu tượng trong các ngôn ngữ khác, nhưng trong Swift, chúng đóng một vai trò lớn hơn. Tại sao?

  • Các kiểu class có thể phù hợp với nhiều hơn một giao thức.
  • Các protocol extension cũng có thể có các hành vi mặc định từ nhiều protocol khác nhau.
  • Không giống như đa kế thừa trong các ngôn ngữ lập trình khác, phần mở rộng giao thức không thêm bất kỳ trạng thái bổ sung nào.
  • Các giao thức có thể được chấp nhận bởi các lớp, struct và enum, trong khi các lớp base và lớp con kế thừa chỉ bị giới hạn ở các loại class.
  • Các giao thức cho phép lập mô hình hồi quy với các phần mở rộng được thêm vào các loại hiện có.

Vậy chúng ta có thể nói protocol là trái tim của swift. Vì vậy hãy học càng nhiều càng tốt từ thư viện tiêu chuẩn – đó là một nguồn kiến ​​thức tuyệt vời! Quan trọng nhất, hãy cho bản thân cơ hội lập trình theo hướng giao thức và tự tìm hiểu cách nó có thể cải thiện quy trình làm việc của bạn.

Bài viết gốc được đăng tải tại codetoanbug.comfanpage

Có thể bạn quan tâm:

Xem thêm Việc làm IT hấp dẫn trên Station D

Bài viết liên quan

Bộ cài đặt Laravel Installer đã hỗ trợ tích hợp Jetstream

Bộ cài đặt Laravel Installer đã hỗ trợ tích hợp Jetstream

Bài viết được sự cho phép của tác giả Chung Nguyễn Hôm nay, nhóm Laravel đã phát hành một phiên bản chính mới của “ laravel/installer ” bao gồm hỗ trợ khởi động nhanh các dự án Jetstream. Với phiên bản mới này khi bạn chạy laravel new project-name , bạn sẽ nhận được các tùy chọn Jetstream. Ví dụ: API Authentication trong Laravel-Vue SPA sử dụng Jwt-auth Cách sử dụng Laravel với Socket.IO laravel new foo --jet --dev Sau đó, nó sẽ hỏi bạn thích stack Jetstream nào hơn: Which Jetstream stack do you prefer? [0] Livewire [1] inertia > livewire Will your application use teams? (yes/no) [no]: ... Nếu bạn đã cài bộ Laravel Installer, để nâng cấp lên phiên bản mới bạn chạy lệnh: composer global update Một số trường hợp cập nhật bị thất bại, bạn hãy thử, gỡ đi và cài đặt lại nha composer global remove laravel/installer composer global require laravel/installer Bài viết gốc được đăng tải tại chungnguyen.xyz Có thể bạn quan tâm: Cài đặt Laravel Làm thế nào để chạy Sql Server Installation Center sau khi đã cài đặt xong Sql Server? Quản lý các Laravel route gọn hơn và dễ dàng hơn Xem thêm Tuyển dụng lập trình Laravel hấp dẫn trên Station D

By stationd
Principle thiết kế của các sản phẩm nổi tiếng

Principle thiết kế của các sản phẩm nổi tiếng

Tác giả: Lưu Bình An Phù hợp cho các bạn thiết kế nào ko muốn làm code dạo, design dạo nữa, bạn muốn cái gì đó cao hơn ở tầng khái niệm Nếu lập trình chúng ta có các nguyên tắc chung khi viết code như KISS , DRY , thì trong thiết kế cũng có những nguyên tắc chính khi làm việc. Những nguyên tắc này sẽ là kim chỉ nam, nếu có tranh cãi giữa các member trong team, thì cứ đè nguyên tắc này ra mà giải quyết (nghe hơi có mùi cứng nhắc, mình thì thích tùy cơ ứng biến hơn) Tìm các vị trí tuyển dụng designer lương cao cho bạn Nguyên tắc thiết kế của GOV.UK Đây là danh sách của trang GOV.UK Bắt đầu với thứ user cần Làm ít hơn Thiết kế với dữ liệu Làm mọi thứ thật dễ dàng Lặp. Rồi lặp lại lần nữa Dành cho tất cả mọi người Hiểu ngữ cảnh hiện tại Làm dịch vụ digital, không phải làm website Nhất quán, nhưng không hòa tan (phải có chất riêng với thằng khác) Cởi mở, mọi thứ tốt hơn Bao trừu tượng luôn các bạn, trang Gov.uk này cũng có câu tổng quát rất hay Thiết kế tốt là thiết kế có thể sử dụng. Phục vụ cho nhiều đối tượng sử dụng, dễ đọc nhất nhất có thể. Nếu phải từ bỏ đẹp tinh tế – thì cứ bỏ luôn . Chúng ta tạo sản phẩm cho nhu cầu sử dụng, không phải cho người hâm mộ . Chúng ta thiết kế để cả nước sử dụng, không phải những người đã từng sử dụng web. Những người cần dịch vụ của chúng ta nhất là những người đang cảm thấy khó sử dụng dịch...

By stationd
Hiểu về trình duyệt – How browsers work

Hiểu về trình duyệt – How browsers work

Bài viết được sự cho phép của vntesters.com Khi nhìn từ bên ngoài, trình duyệt web giống như một ứng dụng hiển thị những thông tin và tài nguyên từ server lên màn hình người sử dụng, nhưng để làm được công việc hiển thị đó đòi hỏi trình duyệt phải xử lý rất nhiều thông tin và nhiều tầng phía bên dưới. Việc chúng ta (Developers, Testers) tìm hiểu càng sâu tầng bên dưới để nắm được nguyên tắc hoạt động và xử lý của trình duyệt sẽ rất hữu ích trong công việc viết code, sử dụng các tài nguyên cũng như kiểm thử ứng dụng của mình. Cách để npm packages chạy trong browser Câu hỏi phỏng vấn mẹo về React: Component hay element được render trong browser? Khi hiểu được cách thức hoạt động của trình duyệt chúng ta có thể trả lời được rất nhiều câu hỏi như: Tại sao cùng một trang web lại hiển thị khác nhau trên hai trình duyệt? Tại sao chức năng này đang chạy tốt trên trình duyệt Firefox nhưng qua trình duyệt khác lại bị lỗi? Làm sao để trang web hiển thị nội dung nhanh và tối ưu hơn một chút?… Hy vọng sau bài này sẽ giúp các bạn có một cái nhìn rõ hơn cũng như giúp ích được trong công việc hiện tại. 1. Cấu trúc của một trình duyệt Trước tiên chúng ta đi qua cấu trúc, thành phần chung và cơ bản nhất của một trình duyệt web hiện đại, nó sẽ gồm các thành phần (tầng) như sau: Thành phần nằm phía trên là những thành phần gần với tương tác của người dùng, càng phía dưới thì càng sâu và nặng về xử lý dữ liệu hơn tương tác. Nhiệm...

By stationd
Thị trường EdTech Vietnam- Nhiều tiềm năng nhưng còn bị bỏ ngỏ tại Việt Nam

Thị trường EdTech Vietnam- Nhiều tiềm năng nhưng còn bị bỏ ngỏ tại Việt Nam

Lĩnh vực EdTech (ứng dụng công nghệ vào các sản phẩm giáo dục) trên toàn cầu hiện nay đã tương đối phong phú với nhiều tên tuổi lớn phân phối đều trên các hạng mục như Broad Online Learning Platforms (nền tảng cung cấp khóa học online đại chúng – tiêu biểu như Coursera, Udemy, KhanAcademy,…) Learning Management Systems (hệ thống quản lý lớp học – tiêu biểu như Schoology, Edmodo, ClassDojo,…) Next-Gen Study Tools (công cụ hỗ trợ học tập – tiểu biểu như Kahoot!, Lumosity, Curriculet,…) Tech Learning (đào tạo công nghệ – tiêu biểu như Udacity, Codecademy, PluralSight,…), Enterprise Learning (đào tạo trong doanh nghiệp – tiêu biểu như Edcast, ExecOnline, Grovo,..),… Hiện nay thị trường EdTech tại Việt Nam đã đón nhận khoảng đầu tư khoảng 55 triệu đô cho lĩnh vực này nhiều đơn vị nước ngoài đang quan tâm mạnh đến thị trường này ngày càng nhiều hơn. Là một trong những xu hướng phát triển tốt, và có doanh nghiệp đã hoạt động khá lâu trong ngành nêu tại infographic như Topica, nhưng EdTech vẫn chỉ đang trong giai đoạn sơ khai tại Việt Nam. Tại Việt Nam, hệ sinh thái EdTech trong nước vẫn còn rất non trẻ và thiếu vắng nhiều tên tuổi trong các hạng mục như Enterprise Learning (mới chỉ có MANA), School Administration (hệ thống quản lý trường học) hay Search (tìm kiếm, so sánh trường và khóa học),… Với chỉ dưới 5% số dân công sở có sử dụng một trong các dịch vụ giáo dục online, EdTech cho thấy vẫn còn một thị trường rộng lớn đang chờ được khai phá. *** Vừa qua Station D đã công bố Báo cáo Vietnam IT Landscape 2019 đem đến cái nhìn toàn cảnh về các ứng dụng công...

By stationd