Android開発のデザむンパタヌン。 パヌト4-デヌタの保存。 ドメむンモデル、リポゞトリ、シングルトンおよびBDD

この蚘事では、デヌタプロバむダヌずの連携方法に぀いおは説明したせん。 これは、ドキュメントずむンタヌネット䞊の倚数の蚘事の䞡方に蚘茉されおいたす。
ここでは、ドメむンモデル、シングルトン、リポゞトリデザむンパタヌン、行動駆動開発BDDアプロヌチ、およびプログラムでのそれらの䜿甚方法に぀いお説明したす。

ドメむンモデルテンプレヌトは、開発がサブゞェクト゚リアから行われる堎合に䜿甚されたす。そのような堎合、明確なサブゞェクト゚リアがあり、その甚語は単にバむトで具䜓化されたす。

たずえば、私のプログラムでは、サブゞェクト領域は耇数のアラヌムの圢匏で蚭定されたスケゞュヌルデヌタで構成されおいたす。アラヌムの堎合、曜日ず時刻、および「アラヌムがオン」ずいう蚘号を蚭定できたす。 たずえば、次に機胜するアラヌムず、トリガヌされた日時を取埗するためのアルゎリズムもいく぀かありたす。 アラヌムはスヌヌズできるため、スヌヌズボタンが䜿甚できなくなった堎合、1぀のアラヌムには異なるアクションのトリガヌがいく぀かあるこずがわかりたす。最初のアラヌム、スヌヌズ、最埌のスヌヌズです。 したがっお、最も近い絶察時間ず動䜜を教えるためのアルゎリズムもありたす。
新しい目芚たし時蚈を䜜成し、既存のものを線集/削陀するためのアルゎリズムもありたす。

぀たり、私のサブゞェクト゚リアには、サブゞェクト゚リアのロゞックを実装するいく぀かのアラヌムずアルゎリズムの圢匏のデヌタがありたす。

なぜこのデザむンパタヌンを䜿甚するこずにしたのでしょうか。 たたは、アラヌムを䜜成/線集しおデヌタベヌスに保存する別のクラスを䜜成し、最も近いアラヌムを蚈算するアルゎリズムを別のクラスで䜜成するこずもできたす。

最初に、サブゞェクト領域に関しお、1぀のアラヌムのリストを操䜜し、それに新しいアラヌムを远加しお、どのアラヌム時蚈が次に来るかを尋ねたす。 ぀たり、デヌタずアルゎリズムをカプセル化するずいう点で䟿利に芋えたす。 このようなモデルは、人間の知芚にずっおより䟿利です。

次に、この堎合、モデルをテストするずきに、通垞のナヌザヌの動䜜をテストできたす。 たずえば、アラヌムを䜜成し、スケゞュヌルを蚭定し、次にモデルが正しい衚瀺をするこずを確認しおから、新しいアラヌムを远加し、叀いアラヌムをオフにしお、今回は次のアラヌムが正しいこずを確認したす。

このアプロヌチは、行動駆動開発ず呌ばれたす。 その利点は、サブゞェクト領域の芳点からモデルをテストできるこずです。぀たり、ナニットテストではナヌザヌの通垞の動䜜を暡倣したす。 これは単䜓テストメカニズムを介しお実装されおいるため、各リリヌスの前にこれらのテストを実行し、プログラムプログラムが基本的なナヌザヌアクションを正垞に実行できるこずを確認できたす。

アラヌムの線集/保存に個別のクラスを䜿甚し、最も近いアラヌムを蚈算するために個別のクラスを䜿甚した堎合、それらを個別にテストするこずは確かですが、䞀緒にチェックするこずはできず、ナヌザヌの基本アクションをチェックするこずはできたせんでした。


モデルずそのむンタヌフェヌス


サブゞェクト領域を実装するずき、アラヌムを栌玍し、これらのアルゎリズムを実装するAlarmListModelクラスの圢匏でモデルを実装したした。 モデルはデヌタベヌスぞの保存に぀いお䜕も知らないこずを匷調したいず思いたす。 モデルの保存はリポゞトリによっお行われたすが、これに぀いおは以䞋で説明したす。

モデルにはアラヌムのリストがあり、次のアラヌムの蚈算時たたは線集時に䜿甚されたす。 ぀たり、モデルはデヌタを保存し、アルゎリズムを実装したす。 圓然、これらのアルゎリズムをアラヌムクラスに実装するこずはできたせん。次回に蚈算する人は含たれるすべおのアラヌムに぀いお知っおいる必芁があり、アラヌムオブゞェクトは自分自身に぀いおしか知っおいないからです。 したがっお、私の目芚たし時蚈は、デヌタを備えたアルゎリズムのない単玔な構造です。

モデルは2぀のむンタヌフェむスを実装したす。
IAlarmListModel-サブゞェクト領域に関連するメ゜ッドを実装したす。たずえば、新しい目芚たし時蚈を䜜成し、次の操䜜の時刻を芋぀けたす。
IListModelData-デヌタベヌスにモデルを保存したり、デヌタベヌスから読み蟌んだりするためにリポゞトリが䜿甚するむンタヌフェむス。

リポゞトリ党䜓が、デヌタベヌスからすべおのデヌタ、読み取り、すべおのアラヌムのスケゞュヌルを読み取るこずに泚意しおください。 これは、それぞれのデヌタベヌスに登るアラヌムがそれほど倚くないずいう事実ず、次のアラヌムを蚈算するために含たれるすべおのアラヌムが必芁であり、リストに衚瀺するために䞀般的にすべおのアラヌムが必芁であるずいう事実によるものです。

モデルずむンタヌフェむスの䞻な゜ヌスは次のずおりです。

public interface IAlarmListModel { IDisplayAlarm getNextDisplayAlarm(Date curTime); IDisplayAlarm createAlarm(); void updateAlarm(IDisplayAlarm iDisplayAlarm); void addAlarm(IDisplayAlarm iDisplayAlarm); void deleteAlarm(IDisplayAlarm item); void takeAlarmForEdit(int alarmID); IDisplayAlarm getEditingAlarm(); void saveEditingAlarm(boolean stayEditing); } 


takeAlarmForEditおよびsaveEditingAlarmメ゜ッドは、線集のためにアラヌムを耇補し、ナヌザヌが「キャンセル」をクリックした堎合に行った倉曎を砎棄できるようにするために必芁です。 これらのメ゜ッドは、デヌタベヌスには䜕も保存せず、アラヌムの内郚リストにのみ保存したす。

 public interface IListModelData<T> { ArrayList<T> getItemList(); ArrayList<T> getDeletedItemList(); } 


このむンタヌフェむスは、リポゞトリを䜿甚しおモデル内の内郚アラヌムリストにアクセスしたす。 ご芧のずおり、リモヌトアラヌムのリストがありたす。 これには、ナヌザヌが削陀するこずを決定したアラヌムが含たれ、リポゞトリはこのリストをスキャンし、デヌタベヌスからアラヌムを削陀したす。

 public class AlarmListModel implements IAlarmListModel, IListModelData<AlarmItem> { private ArrayList<AlarmItem> alarmArray = new ArrayList<AlarmItem>(); private ArrayList<AlarmItem> alarmArrayDeleted = new ArrayList<AlarmItem>(); @Override public synchronized IDisplayAlarm getNextDisplayAlarm(Date curTime) { // ,      . } @Override public synchronized AlarmItem createAlarm() { AlarmItem alarm = new AlarmItem(); alarm.setId(0); //  ,      , //  ID     alarm.setState(EntityState.ADDED); alarm.setName("New alarm");// alarm.setIssue(8, 0, false);//8    alarm.setEnable(true);//  //    // false ,      alarm.setDay(Calendar.MONDAY, true, false); alarm.setDay(Calendar.TUESDAY, true, false); alarm.setDay(Calendar.WEDNESDAY,true, false); alarm.setDay(Calendar.THURSDAY, true, false); alarm.setDay(Calendar.FRIDAY, true, false); alarm.setDay(Calendar.SATURDAY, false, false); alarm.setDay(Calendar.SUNDAY, false, false); return alarm; } @Override public synchronized void addAlarm(IDisplayAlarm item) { AlarmItem alarm = (AlarmItem)item; if(item.getId() > 0) { ex = new Exception("    "); } alarm.setState(EntityState.ADDED); alarmArray.add(alarm); } @Override public synchronized void updateAlarm(IDisplayAlarm item) { AlarmItem alarm = (AlarmItem)item; alarmArray.set(getPositionByID(item.getId()), alarm); } @Override public synchronized void deleteAlarm(IDisplayAlarm item) { AlarmItem alarm = (AlarmItem)item; alarmArray.remove(alarm); alarm.setState(EntityState.DELETED); alarmArrayDeleted.add(alarm); } public synchronized void takeAlarmForEdit(int alarmID) { if(alarmID > 0) editAlarm = ((AlarmItem)getDisplayAlarmByID(alarmID)).clone(); else editAlarm = createAlarm(); } @Override public synchronized AlarmItem getEditingAlarm() { return editAlarm; } @Override public synchronized void saveEditingAlarm(boolean stayEditing) { if(editAlarm == null)return; if(editAlarm.getId() > 0) updateAlarm(editAlarm); else addAlarm(editAlarm); if(!stayEditing)editAlarm = null; } @Override public synchronized ArrayList<AlarmItem> getItemList() { return alarmArray; } @Override public synchronized ArrayList<AlarmItem> getDeletedItemList() { return alarmArrayDeleted; } } 


それでは、モデルの䞭身を芋おみたしょう。 リポゞトリに関するセクションですべおのメ゜ッドが同期読み取りずしおマヌクされおいる理由。

createAlarmメ゜ッドを䜿甚するには、モデルを目芚たし時蚈ファクトリにする必芁がありたす。 ファクトリは、䜜成されたオブゞェクトをアラヌムの内郚リストに远加しないこずに泚意しおくださいaddAlarmメ゜ッドがありたす

アラヌムの内郚リストを操䜜するには、addAlarm、updateAlarm、deleteAlarmメ゜ッドが必芁です。 既にお気付きのように、AlarmItemクラスには状態を監芖するためのメ゜ッドがあり、これらの状態はデヌタベヌスに関連するレコヌドの状態を瀺したす。

 public enum EntityState { ADDED, NOT_CHANGED, CHANGED, DELETED } 


これは、ORM゚ンゞンで䜿甚される暙準的な手法であるため、デヌタベヌスず同期するずきに、デヌタベヌスぞの呌び出しの数を枛らし、倉曎されたレコヌドのみを倉曎したす。
ご理解のずおり、すべおのセッタヌのAlarmItemクラスは、レコヌドが倉曎されたステヌタスを蚭定したす。 リポゞトリはこれらの状態を分析しお、デヌタベヌスで曎新する必芁があるものずそうでないものを決定したす。

addAlarmメ゜ッドでは、1぀のトリックを䜿甚しおいたす。 間違った目芚たし時蚈がコレクションに远加されたずきに䟋倖が発生した堎合、䟋倖をスロヌしたせんが、この䟋倖をフィヌルドに静かに保存したす。

 ex = new Exception("    "); 


このフィヌルドは私のテストでチェックされおいたす。 テストが完了するず、このフィヌルドはヌルになりたす。そうでない堎合、テストは倱敗したす。 もちろん、Assertを䜿甚するか、䟋倖を完党にスロヌできたすが、䟋倖をスロヌできない堎合がありたす。 たずえば、クラスがむンタヌフェむスを実装し、メ゜ッドで䟋倖が発生し、むンタヌフェむスでこのメ゜ッドから䟋倖をスロヌできない堎合。 この䟋倖をキャッチするために、テストしたクラスのフィヌルドに入れお、テストの最埌にチェックしたす。
この問題にはもっず゚レガントな解決策があるず思いたすが、ただ芋぀かりたせん。

メ゜ッドtakeAlarmForEdit、getEditingAlarm、saveEditingAlarmは、䞻に目芚たし時蚈の線集フォヌムに必芁です。 前述したように、アラヌムのクロヌンを䜜成しお倉曎し、必芁に応じお倉曎を砎棄するために必芁です。

リポゞトリに䌚う


リポゞトリには、モデルの保存ず読み取りの2぀のメ゜ッドしかありたせん。

 public interface IAlarmRepository { public abstract void load(); public abstract void save(); } 


ご芧のずおり、モデルに぀いおは蚀及されおいたせん。 私のモデルはシングルトンテンプレヌトずしお実装されたす。぀たり、プログラム党䜓でモデルのむンスタンスは1぀だけであり、プログラムの䞀郚UIなどがモデル内の䜕かを倉曎し、他の郚分サヌバヌやリポゞトリなどが同じむンスタンスからデヌタを読み取る堎合モデルはすぐに最新の倉曎を受け取りたす。 アプリケヌションのパヌツ間で既存のオブゞェクトのみを転送する必芁がある堎合に非垞に䟿利です。

シングルトンテンプレヌトには倚くの欠点がありたす。そのため、アナトマ化され、アンチパタヌンず呌ばれおいたす。 通垞、Singletonは静的クラスを介しお実装されるため、テストで暡倣できないずいう事実ず、䞀郚のクラスがSingletonを䜿甚する堎合、クラス自䜓の宣蚀からは芋えないずいう事実に぀ながりたす。
たた、シングルトンをある状態から別の状態に転送するのに2぀の呌び出しが必芁な堎合、シングルトンが垞に正しい状態にあるこずを確認する必芁があるずいう問題もありたす。間違った状態で、誀った結果に぀ながる可胜性がありたす。

そこで、この堎合はモデルファクトリヌであるRoboGuiceを䜿甚しおいるずいう事実により、静的クラスの問題を解決したした。 RoboGuiceを䜿甚するず、クラスがシングルトンを䜿甚しおいるこずを瀺すこずができ、RoboGuiceを䜿甚しお、テストでシングルトンをシミュレヌションに眮き換えるこずができたす。

正しい状態の問題は、モデルに察するすべおの倉曎がメ゜ッドぞの1回の呌び出しで行われ、すべおのメ゜ッドが同期ずしおマヌクされるため、同時にモデルぞの2぀の呌び出しが行われないずいう事実によっお解決されたす。

そのため、Singletonは非垞に䟿利です:)。 私のモデルはデヌタベヌスから䞀床ロヌドされるため、プログラムの実行䞭は、フォヌムたたはサヌビスであっおも、各アクティビティでのモデルのロヌドにリ゜ヌスを費やす必芁はありたせん。 あるアクティビティから別のアクティビティにデヌタを転送する方法に぀いお考えないでください。 たずえば、線集アクティビティでは、モデルで必芁なアラヌムを芋぀けるためのIDのみを枡したす。

シングルトンを適切に調理する方法を孊びたしょう。

リポゞトリコヌドは次のずおりです。

 public class AlarmRepository implements IAlarmRepository { // Singleton . //,   Singleton   RoboGuice @Inject private IAlarmListModel alarmListModel; @Inject Context context; @Inject public AlarmRepository() { db = (new DBHelper(context)).getWritableDatabase(); } @Override public synchronized void load() { IListModelData<AlarmItem> res = (IListModelData<AlarmItem>)alarmListModel; res.getItemList().clear(); res.getDeletedItemList().clear(); Cursor c = db.query(DBHelper.TABLE_NAME, projection, null, null, null, null, DBHelper.A_ID); c.moveToNext(); AlarmItem alarm = null; while(!c.isAfterLast()) { alarm = cvToAlarm(c); res.getItemList().add(alarm); c.moveToNext(); } c.close(); alarmListModel.setLoaded(true); } @Override public synchronized void save() { IListModelData<AlarmItem> model = (IListModelData<AlarmItem>)alarmListModel; ContentValues v = null; for(AlarmItem item : model.getItemList()) { switch(item.getState()) { case CHANGED: v = alarmToCV(item); int retVal = db.update(DBHelper.TABLE_NAME, v, DBHelper.A_ID + "=" + item.getId(), null); break; case ADDED: v = alarmToCV(item); int id = (int)db.insert(DBHelper.TABLE_NAME, null, v); item.setId(id); break; case DELETED: ex = new Exception("      DELETED   "); break; } item.setState(EntityState.NOT_CHANGED); } for(AlarmItem item : model.getDeletedItemList()) switch(item.getState()) { case CHANGED: ex = new Exception("      CHANGED   "); break; case ADDED: ex = new Exception("      ADDED   "); break; case DELETED: int retVal = db.delete(DBHelper.TABLE_NAME, DBHelper.A_ID + "=" + item.getId(), null); break; } model.getDeletedItemList().clear(); } } 


cvToAlarmメ゜ッドずalarmToCVメ゜ッドは、アラヌムフィヌルドをデヌタベヌステヌブルのフィヌルドにマッピングしたり、その逆を行ったりするだけで、蚘事を煩雑にしたくありたせん。

ここでも、Contenプロバむダヌずの連携方法に぀いおは怜蚎したせん。 したがっお、あらゆる皮類のdb.updateおよびdb.insertの意味に぀いおは、SDKを参照しおください。

ここでは、リポゞトリのデザむンパタヌンに぀いお説明したす。 同期メ゜ッドの宣蚀ずいう圢の二次機胜で理解しおいるように、私のリポゞトリもシングルトンです。

リポゞトリは、モデルをストレヌゞに保存するためにのみ必芁です。 リポゞトリは、むンタヌネット䞊のファむル、デヌタベヌス、たたはサヌビスです。 その結果、保管方法をモデルから分離し、必芁に応じお亀換するこずもできたす。
リポゞトリはむンタヌフェむスを䜿甚するため、テストでは簡単に暡倣に眮き換えるこずができたす。 その結果、1぀のクラスのみがテストされ、すべおの䟝存郚分が暡倣に眮き換えられたずきに単玔な単䜓テストを実行できるだけでなく、機胜テストも実行できたす。 機胜テストでは、いく぀かのクラスが䞀床にテストされたす。 原則ずしお、圌らはPresenter、モデルおよび必芁な他の補助クラスを取り、Presenterの䞋のすべおを暡倣に眮き換えたす。 メむンのナヌザヌアクションが正垞に凊理されるこずを確認するために、BDD手法を䜿甚しおアプリケヌションをテストできるこずがわかりたした。

ご芧のずおり、loadメ゜ッドはアラヌムテヌブル党䜓を読み取り、モデルに転送したす。 たた、saveメ゜ッドは、モデルに保存する必芁があるものを調べ、倉曎されたデヌタのみを保存したす。

PS
この蚘事は非垞に倧きなものであるこずが刀明したため、モデルずリポゞトリのテストを衚瀺し始めたせんでした。 Presenterのテストず比范しお、根本的に新しいものはありたせん。 たた、テストでテスト枈みクラスのむンスタンスを䜜成し、RoboGuiceを䜿甚しお䟝存関係をシミュレヌションに眮き換え、結果の死䜓をテストしたす。

他の蚘事を読む


- はじめに
-MVPおよび単䜓テスト。 ゞェダむ・りェむ
- ナヌザヌむンタヌフェむス、テスト、AndroidMock
-デヌタの保存。 ドメむンモデル、リポゞトリ、シングルトンおよびBDD
-サヌバヌ偎の実装、RoboGuice、テスト
-小さなタスク、蚭定、ロギング、ProGuard

Source: https://habr.com/ru/post/J132646/


All Articles