Model ve Entity Kavramları ile Aralarındaki Farklar
Merhabalar. Bu yazıda, model sınıfları ile entity sınıfları arasındaki tanımları ele alacak ve ardından bu benzer sınıfların arasındaki farkları inceleyip örneklerle açıklayacağız.
Bu anlatımda, Dart programlama dilinde yazılmış olsa da, birçok farklı programlama dilinde kullanılabilen genel bir mantık oluşturmayı hedefliyoruz.
Model Nedir?
Model sınıfları, verileri saklamak ve kullanmak için temel araçlardır. Bu veriler, uzak sunuculardan veya kullanıcı giriş aygıtlarından (örneğin dokunmatik panel, klavye, mikrofon, vb.) alınabilir. Ancak model sınıflarını kullanmadan önce kötü örneklerde neden ihtiyaç duyduğumuzu pekiştirelim.
Örnek olarak, bir servisten aldığımız kullanıcı bilgilerini düşünelim. Bu bilgileri JSON formatında aldık diyelim.
{
"id": "1",
"title": "Burası başlık",
"description":"Lorem ipsum dolor sit amet"
}
Bu JSON objesini elde etmek için API isteklerini gerçekleştiren bir yöntemi oluşturalım
Future<dynamic> getUserInfo() async {
var url = "url";
var response = await apiBaseHelper.get(url: url);
return response;
}
Şimdi, elde ettiğimiz bu veriyi nasıl kullanabileceğimize odaklanalım.
String title = response["title"];
“title” anahtarının her zaman bir değer içerdiğini biliyoruz ve bu oldukça kullanışlı gibi görünüyor. Ancak, daha karmaşık bir JSON objesi içinde “title” anahtarını bulmak ve kullanmak çok zor olabilir.
{
"glossary": {
"title": "example glossary",
"GlossDiv": {
"title": "S",
"GlossList": {
"GlossEntry": {
"ID": "SGML",
"SortAs": "SGML",
"GlossTerm": "Standard Generalized Markup Language",
"Acronym": "SGML",
"Abbrev": "ISO 8879:1986",
"GlossDef": {
"para": "A meta-markup language, used to create markup languages such as DocBook.",
"GlossSeeAlso": ["GML", "XML"]
},
"GlossSee": "markup"
}
}
}
}
}
String glossTerm = response["glossary"]["GlossDiv"]["GlossList"]["GlossEntry"]["GlossTerm"];
‘GlossTerm’ anahtarına erişmek oldukça zorlayıcı olacak ve bu anahtarı birden fazla yerde kullanmak, projenin yönetimini karmaşık hale getirecek. Şimdi, tüm bu zorlukları nasıl aşabileceğimize Model sınıfları ile bir göz atalım. Şimdi servisten şu şekilde bir JSON yanıt alalım
{
"squadName": "Super hero squad",
"homeTown": "Metro City",
"formed": 2016,
"secretBase": "Super tower",
"active": true,
"members": [
{
"name": "Molecule Man",
"age": 29,
"secretIdentity": "Dan Jukes",
"powers": ["Radiation resistance", "Turning tiny", "Radiation blast"]
},
{
"name": "Madame Uppercut",
"age": 39,
"secretIdentity": "Jane Wilson",
"powers": [
"Million tonne punch",
"Damage resistance",
"Superhuman reflexes"
]
},
{
"name": "Eternal Flame",
"age": 1000000,
"secretIdentity": "Unknown",
"powers": [
"Immortality",
"Heat Immunity",
"Inferno",
"Teleportation",
"Interdimensional travel"
]
}
]
}{
"squadName": "Super hero squad",
"homeTown": "Metro City",
"formed": 2016,
"secretBase": "Super tower",
"active": true,
"members": [
{
"name": "Molecule Man",
"age": 29,
"secretIdentity": "Dan Jukes",
"powers": ["Radiation resistance", "Turning tiny", "Radiation blast"]
},
{
"name": "Madame Uppercut",
"age": 39,
"secretIdentity": "Jane Wilson",
"powers": [
"Million tonne punch",
"Damage resistance",
"Superhuman reflexes"
]
},
{
"name": "Eternal Flame",
"age": 1000000,
"secretIdentity": "Unknown",
"powers": [
"Immortality",
"Heat Immunity",
"Inferno",
"Teleportation",
"Interdimensional travel"
]
}
]
}
class ExampleModel {
final String squadName;
final String homeTown;
final int formed;
final String secretBase;
final bool active;
final List<Member> members;
ExampleModel({
required this.squadName,
required this.homeTown,
required this.formed,
required this.secretBase,
required this.active,
required this.members,
});
factory ExampleModel.fromJson(Map<String, dynamic> json) => ExampleModel(
squadName: json["squadName"],
homeTown: json["homeTown"],
formed: json["formed"],
secretBase: json["secretBase"],
active: json["active"],
members:
List<Member>.from(json["members"].map((x) => Member.fromJson(x))),
);
}
class Member {
final String name;
final int age;
final String secretIdentity;
final List<String> powers;
Member({
required this.name,
required this.age,
required this.secretIdentity,
required this.powers,
});
factory Member.fromJson(Map<String, dynamic> json) => Member(
name: json["name"],
age: json["age"],
secretIdentity: json["secretIdentity"],
powers: List<String>.from(json["powers"].map((x) => x)),
);
}
JSON verileri için Dart dilinde sınıf modelleri oluşturmak için birçok kaynak ve araç bulunmaktadır. Benim kullandığım ve önerdiğim araç ise quicktype adlı sitedir.
Future<ExampleModel> getExampleJson() async {
var url = "url";
var response = await apiBaseHelper.get(url: url);
return ExampleModel.fromJson(response);
}
JSON verilerini servisten alıp bu verileri bir modele aktarmak için yazdığımız yöntem.
ExampleModel exampleModel = ExampleModel.fromJson(response);
String name = exampleModel.members[0].name;
bool isActive = exampleModel.active;
Model sınıflarına veriyi yükledikten sonra, istediğimiz anahtara güvenle erişebilir hale geliyoruz. Model sınıflarının işlevini büyük ölçüde açıkladıktan sonra, şimdi de Entity sınıflarının ne olduğunu ele alalım.
Entity Nedir?
Entity sınıfları, Model sınıflarına oldukça benzer. Model sınıfları, bir API’nin hem mobil platformlar hem de diğer platformlar gibi çeşitli platformlarda kullanılabilmesi için tasarlanır. Bu çoklu platformlar, genellikle büyük miktarda veri sağlar ve ilerleyen sürümlerde işe yarayabileceği gibi, belirli platformlara özgü özelleştirmeler de içerebilir.
Bu noktada, örneğin bir mobil uygulama geliştiriyoruz ve bu nedenle Model sınıflarını Entity sınıflarına dönüştürmemiz gerekiyor. Entity sınıfları, genellikle ilgili platformlar için Model sınıflarının değiştirilmiş versiyonlarıdır. Şimdi, bu modifikasyonların neden gerektiğine dair bazı önemli nedenlere göz atalım.
- Model sınıfında bir anahtarın diğer katmanlar tarafından görünmesinin gerekmeyebilmesi durumu ortaya çıkabilir.
- Model sınıfında bir anahtarın diğer katmanlar tarafından görünmesi, tehlikeye yol açabilir.
- Birden fazla model dosyasını tek bir kullanım senaryosu için ortak bir yapıya dönüştürme ihtiyacı doğabilir.
- String, integer gibi temel tiplerin, belirli bir dile veya çerçeveye özgü özel bir tipe dönüştürülmesi gerekebilir.
- Servisten aynı veriler tekrar geldiğinde sınıfın bir örneğini yeniden oluşturmamızı önlemek isteyebiliriz.
Kullanım senaryoları, projeye ve alınan verilere bağlı olarak artırılabilir veya azaltılabilir. Ancak en temel kavramları kendi görüşlerimle aktarmış oldum. Şimdi bu maddelere tek tek ayrıntılı bir şekilde değineceğiz.
{
"name": "Molecule Man",
"age": 29,
"gender": "Man",
"secretIdentity": "Dan Jukes",
"powers": ["Radiation resistance", "Turning tiny", "Radiation blast"]
}
Sunucudan alınan JSON verisi aşağıdaki gibidir ve bunu modelleyelim.
class ExampleModel {
final String name;
final int age;
final String gender;
final String secretIdentity;
final List<String> powers;
ExampleModel({
required this.name,
required this.age,
required this.gender,
required this.secretIdentity,
required this.powers,
});
factory ExampleModel.fromJson(Map<String, dynamic> json) => ExampleModel(
name: json["name"],
age: json["age"],
gender: json["gender"],
secretIdentity: json["secretIdentity"],
powers: List<String>.from(json["powers"].map((x) => x)),
);
}
Şimdi bu model sınıfını Entity sınıfına dönüştürelim.
class ExampleEntity {
const ExampleEntity({
required this.name,
required this.age,
required this.gender,
required this.secretIdentity,
required this.powers,
});
final String name;
final int age;
final String gender;
final String secretIdentity;
final List<String> powers;
}
Model sınıfını, Repository katmanıyla Entity’ye dönüştürmemiz gerekiyor. Ancak bu konuya girmek yazının anlaşılabilirliğini düşüreceği ve dikkati dağıtacağı için bu konuya şu an için değinilmeyecektir. Şimdi ise bu dönüşümü gerçekleştirmeden önce aralarındaki ilişkiyi kurmamız gerekiyor.”
class ExampleModel extends ExampleEntity{
final String name;
final int age;
final String gender;
final String secretIdentity;
final List<String> powers;
ExampleModel({
required this.name,
required this.age,
required this.gender,
required this.secretIdentity,
required this.powers,
}):super(
age: age,
gender: gender,
name: name,
powers: powers,
secretIdentity: secretIdentity,
);
factory ExampleModel.fromJson(Map<String, dynamic> json) => ExampleModel(
name: json["name"],
age: json["age"],
gender: json["gender"],
secretIdentity: json["secretIdentity"],
powers: List<String>.from(json["powers"].map((x) => x)),
);
}
class ExampleEntity {
const ExampleEntity({
required this.name,
required this.age,
required this.gender,
required this.secretIdentity,
required this.powers,
});
final String name;
final int age;
final String gender;
final String secretIdentity;
final List<String> powers;
}
ExampleEntity sınıfı, ExampleModel sınıfının süper sınıfı haline getirildi. Bu konunun daha iyi anlaşılması için ‘extends’ ve ‘super’ kelimelerinin Dart dili için küçük bir araştırma yapılması önerilir.
Future<ExampleEntity> modelToEntity() async {
ExampleEntity exampleEntity= await getExampleJson();
return exampleEntity;
}
Model sınıfını Entity sınıfına dönüştürmüş olduk. Şimdi ise yukarıda belirtilen maddelere tek tek bakalım.
İlk iki madde, bir sebeple ‘key’in diğer katmanlar tarafından görünmesini engellemekteydi
class ExampleModel extends ExampleEntity {
final String name;
final String surName;
final int age;
final String gender;
final String secretIdentity;
final List<String> powers;
ExampleModel({
required this.name,
required this.surName,
required this.age,
required this.gender,
required this.secretIdentity,
required this.powers,
}) : super(
age: age,
fullName: "$name $surName",
gender: gender,
powers: powers,
secretIdentity: secretIdentity,
);
factory ExampleModel.fromJson(Map<String, dynamic> json) => ExampleModel(
name: json["name"],
surName: json["surName"],
age: json["age"],
gender: json["gender"],
secretIdentity: json["secretIdentity"],
powers: List<String>.from(json["powers"].map((x) => x)),
);
}
class ExampleEntity {
const ExampleEntity({
required this.fullName,
required this.age,
required this.gender,
required this.secretIdentity,
required this.powers,
});
final String fullName;
final int age;
final String gender;
final String secretIdentity;
final List<String> powers;
}
Model sınıfında, ‘name’ ve ‘surName’ adlı iki özelliğin olduğunu görüyoruz. Ancak, kurduğumuz sistemde diğer katmanlar, örneğin UI tarafından, bu özelliklerin doğrudan veya dolaylı olarak bilinmesine ihtiyaç duymuyor. Bizim için sadece ismin tam hali gereklidir ve Entity sınıfına aktarırken bu iki ismi birleştirerek atıyoruz.
Birden fazla model dosyasını tek bir kullanım senaryosu için ortak bir yapıya dönüştürme ihtiyacı doğabilir
Elimizde iki adet birbirine çok benzeyen JSON dosyası bulunuyor ve bu dosyaları ekranda bir liste halinde göstermemiz isteniyor. Standartlara uygun olarak, her JSON dosyası için mutlaka kendine özgü bir model oluşturmalıyız. Aksi takdirde, ilerideki değişikliklere uyum sağlayamayabiliriz. job??professional gibi Dart dilinde null kontrolü, standartlara uymayacaktır.
{
"first_name": "Fırat",
"last_name": "Kara",
"professional": "Computer engineer"
}
{
"first_name": "Fırat",
"last_name": "Kara",
"job": "Computer engineer"
}
class ExampleModel {
final String firstName;
final String lastName;
final String job;
ExampleModel({
required this.firstName,
required this.lastName,
required this.job,
});
factory ExampleModel.fromJson(Map<String, dynamic> json) => ExampleModel(
firstName: json["first_name"],
lastName: json["last_name"],
job: json["job"],
);
}
class ExampleModel2 {
final String firstName;
final String lastName;
final String professional;
ExampleModel2({
required this.firstName,
required this.lastName,
required this.professional,
});
factory ExampleModel2.fromJson(Map<String, dynamic> json) => ExampleModel2(
firstName: json["first_name"],
lastName: json["last_name"],
professional: json["professional"],
);
}
Bu iki JSON nesnesini iki farklı modele dönüştürdük, şimdi bunları kullanmaya çalıştığımızda şöyle bir zorlukla karşılaşıyoruz.
List<ExampleModel> model1;
List<ExampleModel2> model2;
Bunu ekranda bir bileşen(Widget, Color vs) olarak göstermeye çalıştığımızda, iki farklı bileşen oluşturmak zorunda kalıyoruz. Çünkü bu bileşene ‘Sen hem ExampleModel’sin hem de ExampleModel2'sin’ dediğimizde, if-else yapılarının kontrolü karmaşık hale getirir. Bunun yerine, bu iki modeli tek bir yapı altında toplamaya çalışalım
class ExampleModel extends ExampleEntity{
final String firstName;
final String lastName;
final String job;
ExampleModel({
required this.firstName,
required this.lastName,
required this.job,
}):super(
firstName: firstName,
lastName: lastName,
job: job,
);
factory ExampleModel.fromJson(Map<String, dynamic> json) => ExampleModel(
firstName: json["first_name"],
lastName: json["last_name"],
job: json["job"],
);
}
class ExampleModel2 extends ExampleEntity{
final String firstName;
final String lastName;
final String professional;
ExampleModel2({
required this.firstName,
required this.lastName,
required this.professional,
}):super(
firstName: firstName,
lastName: lastName,
job: professional,
);
factory ExampleModel2.fromJson(Map<String, dynamic> json) => ExampleModel2(
firstName: json["first_name"],
lastName: json["last_name"],
professional: json["professional"],
);
}
class ExampleEntity{
const ExampleEntity({
required this.firstName,
required this.lastName,
required this.job,
});
final String firstName;
final String lastName;
final String job;
}
Görüldüğü üzere, birbirine ne kadar benzese de aslında tamamen farklı olan bu iki sınıfı tek bir çatı altında toplamış olduk.
List<ExampleEntity> exampleList;
Bahsedilen bileşene doğrudan generic tipleme aracılığıyla tip belirlemesi yaparak, onu rahat bir şekilde kullanabiliriz.
String, integer gibi temel tiplerin, belirli bir dile veya çerçeveye özgü özel bir tipe dönüştürülmesi gerekebilir
Servisten bize ‘r’, ‘g’ ve ‘b’ anahtarlarıyla renk kodları geldiğini varsayalım. Bu renk kodlarını Flutter’a özgü olan ‘Color()’ sınıfına aktaralım.
class ColorModel {
final int r;
final int g;
final int b;
ColorModel({
required this.r,
required this.g,
required this.b,
});
factory ColorModel.fromJson(Map<String, dynamic> json) => ColorModel(
r: json["r"],
g: json["g"],
b: json["b"],
);
ColorEntity colorEntity() {
return ColorEntity(color: Color.fromRGBO(r, g, b, 1));
}
}
class ColorEntity {
const ColorEntity({
required this.color,
});
final Color color;
}
UI tarafında ihtiyacımız olan tek şey bir ‘Color’ sınıfıdır. ‘r’, ‘g’, ve ‘b’ değerlerinin ne olduğunu UI katmanının bilmesine hiçbir ihtiyaç yoktur ve renklerle ilgili lojik işlemler lojik katmanlarda gerçekleşmiyorsa, UI katmanına sadece ‘Color’ sınıfını göndermek yeterlidir.
Servisten aynı veriler tekrar geldiğinde sınıfın bir örneğini yeniden oluşturmamızı önlemek isteyebiliriz
Servisten aynı verilerin tekrar geldiğini düşünelim. Bu durumda aynı sınıfı tekrar oluşturmanın bir anlamı yoktur. Var olan sınıfı korumak daha mantıklıdır.
class ColorEntity extends Equatable{
const ColorEntity({
required this.color,
});
final Color color;
@override
List<Object?> get props => [
color,
];
}
Equatable, bir Dart paketidir. Bu işlem, hashCode’lar aracılığıyla paketsiz bir şekilde yapılabilirdi, ancak bu yazının konusu olmadığı için sadece Equatable’den yüzeysel olarak bahsetmek istiyorum. Equatable, değişiklikleri kontrol edip duruma göre nesneyi tekrar örneklendirme kararını alarak performans artırımları sağlar. Elbette, kullanımını ve arkasındaki işleyişi araştırmak faydalı olacaktır.
Unutmayalım, yapılacak projeye göre bu maddeler çok daha fazla genişletilebilir, ancak kendi düşünceme göre bu maddeler oldukça önemlidir.