Questo modello di progettazione di tipo Singleton è un framework fattibile su cui costruire?

0

Sto postando questa domanda qui dopo che è stato determinato come "off-topic" per lo stackoverflow e "troppo ipotetico" per la visualizzazione del codice.

Sto sperimentando diversi modelli di progettazione in stile singleton per un componente di app che ha a che fare con effetti sonori e parlato in tutta l'app.

La mia teoria è che mentre spesso disapprovato, rendendo questi tipi di oggetti globali / statici / singleton-like avrà vantaggi come:

  • L'utilizzo della memoria è più stabile / costante con una previsione di base, rendendo più facili da prevedere gli errori di OOM.
  • Inizializzazioni costose all'interno di questi oggetti (come motori TTS e grandi buffer audio) avverranno una volta sola.
  • I suoni non vengono interrotti dalle transizioni di attività.
  • Perdite di memoria causate da de-e-re-inizializzazioni di oggetti costanti e laggose (ad esempio il motore Google TTS) possono essere evitate avendo un'unica istanza globale.

La mia domanda è: "Il modello di progettazione mostrato nel mio esempio di codice è fattibile / accettabile / solido?" ... e con questo non intendo secondo te - voglio dire c'è qualcuno là fuori con più esperienza di me (non è difficile da trovare) che possa vedere qualche motivo per cui questo codice potrebbe essere soggetto a bug?

Ho usato principalmente l'aiuto di questo articolo .

manifestare:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.boober.speechsingletonsimplifiedunit">

    <application
        android:name="App"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity"
            >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name=".SettingsActivity"
            >

        </activity>
    </application>

    <!-- android:screenOrientation="portrait" -->
</manifest>

Classe di applicazione:

package com.example.boober.speechsingletonsimplifiedunit;

import android.app.Application;
import android.content.Context;
import android.util.Log;

import com.squareup.leakcanary.LeakCanary;

public class App extends Application {

    private static Context appContext;

    public static GodOfSpeechSingleton GOSS;

    @Override
    public void onCreate() {
        super.onCreate();

        // BEGIN LEAK CANARY CODE:
        if (LeakCanary.isInAnalyzerProcess(this)) {
            // This process is dedicated to LeakCanary for heap analysis.
            // You should not init your app in this process.
            return;
        }
        LeakCanary.install(this);
        // END LEAK CANARY CODE, PROCEED...

        Log.i("XXX", "App.onCreate() called.");
        appContext = this;
        GOSS = new GodOfSpeechSingleton();
    }

    public static Context getAppContext() {
        return appContext;
    }

}

Il "GOSS":

package com.example.boober.speechsingletonsimplifiedunit;

import android.speech.tts.TextToSpeech;
import android.util.Log;

import java.util.HashMap;
import java.util.Locale;
import java.util.Random;

public class GodOfSpeechSingleton { // aka "the GOSS"

    private boolean thisClassHasBeenInitAlready = false;
    TextToSpeech tts1;
    boolean tts1IsInit;

    public GodOfSpeechSingleton() {
        Log.i("XXX", "GOSS constructor called.");
    }

    // This method body was created/moved (execution delayed) from the constructor to here
    // because when constructor was called from Application.onCreate(),
    // the code "android.content.Context.getPackageManager()" inside the TTS
    // causes a null pointer exception.
    public void initFromMainActivity(String caller) {
        if (!caller.equals("MainActivity")) { return; }
        if (thisClassHasBeenInitAlready) {
            Log.i("XXX", "GOSS init already.");
            return; }
        thisClassHasBeenInitAlready = true;

        tts1 = new TextToSpeech(App.getAppContext(), new TextToSpeech.OnInitListener() {
            @Override
            public void onInit(int i) {
                tts1IsInit = true;
            }
        }, "com.google.android.tts");

    }

    public void speak(String stringToSpeak, Locale localeToUseForThisUtterance) {
        if (!tts1IsInit) {
            Log.i("XXX", "speak() called, but tts1 not init yet!");
            return;
        }
        tts1.setLanguage(localeToUseForThisUtterance);
        tts1.speak(stringToSpeak, TextToSpeech.QUEUE_FLUSH,  null);
    }

}

MainActivity:

package com.example.boober.speechsingletonsimplifiedunit;

import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;

import java.util.Locale;

public class MainActivity extends AppCompatActivity {

    // LIFECYCLE
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Log.i("XXX", "Main.onCreate() called.");
        App.GOSS.initFromMainActivity("MainActivity");

    }

    @Override
    protected void onDestroy() {
        Log.i("XXX", "Main.onDestroy() called.");
        super.onDestroy();
    }


    // BUTTON PRESSES
    public void settingsButtonPressed(View ignored) {
        Intent intent = new Intent(this, SettingsActivity.class);
        startActivity(intent);
    }

    public void speakButtonPressed(View ignored) {
        App.GOSS.speak("you are in the main activity!",new Locale("en"));
    }


}

SettingsActivity:

package com.example.boober.speechsingletonsimplifiedunit;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;

import java.util.Locale;

public class SettingsActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_settings);

    }

    // BUTTON PRESSES
    public void speakButtonPressed(View ignored) {
        App.GOSS.speak("you are in the settings activity!", new Locale("en"));
    }

}
    
posta Boober Bunz 21.08.2018 - 11:16
fonte

2 risposte

7

È perfettamente logico disporre di una sola istanza di una classe se ha un costo elevato durante l'inizializzazione o se consente operazioni continue come nel tuo caso.

Ma ciò non significa che il resto del sistema dovrebbe sapere questo come un singleton. Può ancora essere iniettato, solo che la parte del sistema che la detiene e la inietta la vede come un singleton.

Potresti persino avere moduli nel tuo codice che devono essere inconsapevoli di un oggetto singleton o meno.

Una connessione al database potrebbe essere un singleton su un dispositivo mobile ma deve essere raggruppata su un server Web.

Lo stesso modulo che necessita di una connessione al database iniettata non dovrebbe sapere se funziona su un server web o su un dispositivo mobile, tutto quello che deve sapere è che può usare la connessione del database iniettato per le sue esigenze.

    
risposta data 21.08.2018 - 12:09
fonte
2

Non si tratta tanto di essere "incline ai bug", quanto di un problema di manutenibilità e testabilità. A lungo termine, è abbastanza probabile che scoprirai che potrebbe essere stato più semplice impostare "più normali" costrutti OOP piuttosto che singleton. D'altra parte, il refactoring è in genere abbastanza semplice in seguito (purché si abbia una base pulita con cui lavorare comunque), quindi non lo conterei come la fine del mondo, ma perché farlo meglio dopo, se lo si può fare meglio adesso? Questo tende ad essere il problema con singleton. Non tanto con gli errori direttamente.

Penso che quello che stai cercando qui è simile a ciò che fa una libreria di contenitori DI / IOC: crea una "istanza accessibile globalmente" di qualcosa che puoi prendere e usare quando necessario (beh, quello è uno dei cose che (possono) fare, comunque, anche se non l'uso primario - l'uso primario è un po 'più disciplinato di quello, e lo sto riducendo molto). L'idea è che non si usano classi singleton, ma invece si registrano le proprie classi all'avvio dell'applicazione e si dice al proprio DI / IOC dove verranno utilizzate, e lo renderà disponibile. Una caratteristica laterale di questi tipi di librerie è che puoi più o meno generalmente catturarne un'istanza ogni volta che vuoi (che sembra essere quello che cerchi).

In ogni caso, usando questo approccio, hai la spesa di complicare le cose un po 'più in anticipo mentre hai il risparmio a lungo termine di avere un progetto più "correttamente" strutturato per testare / mantenere in seguito.

(come parte personale, non tendo a occuparmi di DI più di tanto, preferisco solitamente la semplicità del tuo approccio: è più tardi che la DI ripaga davvero)

    
risposta data 21.08.2018 - 11:59
fonte

Leggi altre domande sui tag