Реализация класса профиля FTP-хоста приложения Android

Это дополнительный вопрос для подключения приложения Android к FTP-серверу на Java. Я пытаюсь создать крошечный менеджер FTP-хоста с FTPHostProfile класс и FTPHostProfiles класс.

Экспериментальная реализация

  • Название проекта: FTPHostManagement

  • FTPHostProfile реализация класса:

    package com.example.ftphostmanagement;
    
    public class FTPHostProfile {
        private String ProfileName = "";
        private String FTPHostName = "";
        private int FTPPort = 21;
        private String FTPUsername = "";
        private String FTPPassword = "";
    
        public FTPHostProfile(String hostnameInput, int portInput, String usernameInput, String passwordInput)
        {
            this.ProfileName = FTPHostName;
            this.FTPHostName = hostnameInput;
            this.FTPPort = portInput;
            this.FTPUsername = usernameInput;
            this.FTPPassword = passwordInput;
        }
    
        public FTPHostProfile(String profileNameInput, String hostnameInput, int portInput, String usernameInput, String passwordInput)
        {
            this.ProfileName = profileNameInput;
            this.FTPHostName = hostnameInput;
            this.FTPPort = portInput;
            this.FTPUsername = usernameInput;
            this.FTPPassword = passwordInput;
        }
    
        public String GetProfilename()
        {
            return this.ProfileName;
        }
    
        public String GetHostname()
        {
            return this.FTPHostName;
        }
    
        public int GetPort()
        {
            return this.FTPPort;
        }
    
        public String GetUsername()
        {
            return this.FTPUsername;
        }
    
        public String GetPassword()
        {
            return this.FTPPassword;
        }
    }
    
  • FTPHostProfiles реализация класса:

    package com.example.ftphostmanagement;
    
    import android.util.Log;
    
    import org.apache.commons.net.ftp.FTP;
    
    import java.util.ArrayList;
    import java.util.stream.Collectors;
    
    public class FTPHostProfiles {
        private ArrayList<FTPHostProfile> Collection = new ArrayList<>();
    
        /// Empty constructor
        public FTPHostProfiles()
        {
    
        }
    
        /// Single FTPHostProfile handler
        public FTPHostProfiles(FTPHostProfile input)
        {
            this.Collection.add(input);
        }
    
        /// Multiple FTPHostProfiles handler
        public FTPHostProfiles(ArrayList<FTPHostProfile> input)
        {
            this.Collection = input;
        }
    
        public FTPHostProfiles(FTPHostProfiles input)
        {
            this.Collection.addAll(input.Collection);
        }
    
        public FTPHostProfiles AddProfile(FTPHostProfile input)
        {
            this.Collection.add(input);
            return this;
        }
    
        public FTPHostProfiles AddProfiles(ArrayList<FTPHostProfile> input)
        {
            this.Collection.addAll(input);
            return this;
        }
    
        public FTPHostProfiles AddProfiles(FTPHostProfiles input)
        {
            this.Collection.addAll(input.Collection);
            return this;
        }
    
        public FTPHostProfile GetProfile(String profileNameInput)
        {
            var filteredResult = this.Collection.stream().filter(element -> (element.GetProfilename().equals(profileNameInput))).collect(Collectors.toList());
            if (filteredResult.stream().count() >= 1)
            {
                return filteredResult.get(0);
            }
            else
            {
                Log.d("GetProfile", "no such item in stored profiles");
                throw new IllegalStateException("no such item in stored profiles");
            }
        }
    
    }
    
  • FTPconnection реализация класса:

    package com.example.ftphostmanagement;
    
    import android.util.Log;
    
    import java.io.IOException;
    import java.net.InetAddress;
    import java.net.SocketException;
    import java.net.UnknownHostException;
    
    import org.apache.commons.net.SocketClient;
    import org.apache.commons.net.ftp.FTP;
    import org.apache.commons.net.ftp.FTPClient;
    import org.apache.commons.net.ftp.FTPReply;
    
    public class FTPconnection {
    
        FTPHostProfiles mFTPHostProfiles = new FTPHostProfiles();
    
        public FTPconnection(FTPHostProfiles input)
        {
            this.mFTPHostProfiles = input;
        }
    
        public FTPClient connectftp(String profilename)
        {
            //  Reference: https://stackoverflow.com/a/8761268/6667035
            FTPClient ftp = new FTPClient();
            try {
                var FTPHostProfile = mFTPHostProfiles.GetProfile(profilename);
    
                //  Reference: https://stackoverflow.com/a/55950845/6667035
                //  The argument of `FTPClient.connect` method is hostname, not URL.
                ftp.connect(FTPHostProfile.GetHostname(), FTPHostProfile.GetPort());
                boolean status = ftp.login(FTPHostProfile.GetUsername(), FTPHostProfile.GetPassword());
                if (status)
                {
                    ftp.enterLocalPassiveMode();
                    ftp.setFileType(FTP.BINARY_FILE_TYPE);
                    ftp.sendCommand("OPTS UTF8 ON");
                }
                System.out.println("status : " + ftp.getStatus());
            } catch (UnknownHostException ex) {
                ex.printStackTrace();
            } catch (SocketException en) {
                en.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
            return ftp;
        }
    }
    
  • Настройка разрешений пользователя

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    
  • AndroidManifest.xml

    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.example.ftphostmanagement">
    
        <application
            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/Theme.FTPHostManagement">
            <activity
                android:name=".MainActivity"
                android:exported="true">
                <intent-filter>
                    <action android:name="android.intent.action.MAIN" />
    
                    <category android:name="android.intent.category.LAUNCHER" />
                </intent-filter>
            </activity>
        </application>
        <uses-permission android:name="android.permission.INTERNET" />
        <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
        <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    </manifest>
    
  • build.gradle

    plugins {
        id 'com.android.application'
    }
    
    android {
        compileSdk 30
    
        defaultConfig {
            applicationId "com.example.ftphostmanagement"
            minSdk 26
            targetSdk 30
            versionCode 1
            versionName "1.0"
    
            testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        }
    
        buildTypes {
            release {
                minifyEnabled false
                proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
            }
        }
        compileOptions {
            sourceCompatibility JavaVersion.VERSION_11
            targetCompatibility JavaVersion.VERSION_11
        }
    }
    
    dependencies {
        implementation fileTree(include: ['*.jar'], dir: 'libs')
        // https://mvnrepository.com/artifact/commons-net/commons-net
        implementation group: 'commons-net', name: 'commons-net', version: '20030805.205232'
        implementation 'androidx.appcompat:appcompat:1.2.0'
        implementation 'com.google.android.material:material:1.3.0'
        implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
        testImplementation 'junit:junit:4.13.2'
        androidTestImplementation 'androidx.test.ext:junit:1.1.2'
        androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
    }
    

Полный код тестирования

  • MainActivity.java выполнение:

    package com.example.ftphostmanagement;
    
    import androidx.appcompat.app.AppCompatActivity;
    
    import android.content.Context;
    import android.os.AsyncTask;
    import android.os.Bundle;
    import android.util.Log;
    import android.widget.Toast;
    
    import org.apache.commons.net.ftp.FTPClient;
    
    public class MainActivity extends AppCompatActivity {
    
        private FTPClient ftpClient;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            new FtpTask().execute();
        }
    
        void ShowToast(String Text, int Duration)
        {
            Context context = getApplicationContext();
            CharSequence text = Text;
            int duration = Duration;
    
            Toast toast = Toast.makeText(context, text, duration);
            toast.show();
        }
    
        //  AsyncTask must be subclassed to be used. The subclass will override at least one method
        //  `doInBackground(Params...)`, and most often will override a second one `onPostExecute(Result)`
        //  Reference: https://developer.android.com/reference/android/os/AsyncTask?authuser=4
        //  Reference: https://stackoverflow.com/a/12447497/6667035
        private class FtpTask extends AsyncTask<Void, Void, FTPClient> {
            //  `doInBackground` invoked on the background thread immediately after `onPreExecute()`
            //  finishes executing. This step is used to perform background computation
            //  that can take a long time. The parameters of the asynchronous task are
            //  passed to this step. The result of the computation must be returned by
            //  this step and will be passed back to the last step. This step can also use
            //  `publishProgress(Progress...)` to publish one or more units of progress.
            //  These values are published on the UI thread, in the `onProgressUpdate(Progress...)`
            //  step.
            protected FTPClient doInBackground(Void... args) {
                FTPHostProfiles ftpHostProfiles = new FTPHostProfiles();
                ftpHostProfiles.AddProfile(new FTPHostProfile(  "Profile1",
                        "Hostname1",
                        21,
                        "Username1",
                        "Password1"));
                ftpHostProfiles.AddProfile(new FTPHostProfile(  "Profile2",
                        "Hostname2",
                        21,
                        "Username2",
                        "Password2"));
    
                FTPconnection ftpConnect = new FTPconnection(ftpHostProfiles);
                FTPClient ftp = ftpConnect.connectftp("Profile1");
                return ftp;
            }
    
            //  `onPostExecute` invoked on the UI thread after the background computation finishes.
            //  The result of the background computation is passed to this step as
            //  a parameter.
            protected void onPostExecute(FTPClient result) {
                Log.v("FTPTask","FTP connection complete");
                ftpClient = result;
                //Where ftpClient is a instance variable in the main activity
                Log.v("Boolean.toString(ftpClient.isConnected())", Boolean.toString(ftpClient.isConnected()));
                ShowToast(Boolean.toString(ftpClient.isConnected()), Toast.LENGTH_SHORT);
            }
        }
    }
    

Все предложения приветствуются.

Сводная информация:

  • На какой вопрос это продолжение?

    Приложение Android подключается к FTP-серверу на Java

  • Какие изменения были внесены в код с момента последнего вопроса?

    В FTPHostProfile класс и FTPHostProfiles реализация класса является ключевой частью этого поста.

  • Почему запрашивается новый обзор?

    Если есть какие-то улучшения, пожалуйста, дайте мне знать.

1 ответ
1

Важное предостережение: я ничего не знаю о специальных соглашениях о кодировании для Android, которые отличаются от обычных java.

Общий

Желательно использовать строчные буквы для акронимов в именах классов. FtpHostProfile легче читать, чем FTPHostProfile.

Классы, которые не были тщательно разработаны для расширения, должны быть окончательными.

Идиоматично, когда имена переменных начинаются со строчной буквы. profileName, так далее.

Все переменные, которые не изменяются после создания объекта, должны быть отмечены. final. Это снижает когнитивную нагрузку на читателя, поскольку он может быть уверен, что не будет переназначен после построения.

Идиоматично, когда имена методов начинаются со строчной буквы.

Идиоматично, когда фигурные скобки находятся на той же строке, что и объявление метода, а не на отдельной строке.

Предпочитайте использовать наиболее подходящий тип в левой части назначений. List вместо ArrayList, так далее.

Нет необходимости присваивать переменным значение, если они будут немедленно перезаписаны в конструкторе.

FTPHostProfile

Нет смысла назначать переменным значение «» «, если вы просто собираетесь перезаписать их в обоих конструкторах.

Почему в первом конструкторе для имени профиля устанавливается значение "" установив для него значение FTPHostName? Если вы хотите, чтобы он был пустым, сделайте его пустым. Это особенно сбивает с толку, потому что FTPHostName переназначается в следующей строке.

Строки длиной более сотни символов читаются с трудом. Подумайте о том, чтобы разбить объявление второго конструктора на несколько строк или, по крайней мере, начать объявления переменных в отдельной строке.

username а также hostname иногда записываются одним словом. Profilename это не английское слово, поэтому его следует разбить на ProfileName. Имена переменных также несовместимы с именами методов.

FTPHostProfiles

Почему конструктор номер 3 требует, чтобы несколько профилей были отправлены в `ArrayList`? Что не так с LinkedList или (ах!) С каким-то набором? Этот конструктор должен иметь коллекцию. То же самое для addProfiles.

Непосредственно присвоение input этого конструктора в Collection плохая идея. Теперь вы потеряли контроль над своими внутренностями — кто бы ни прошел input все еще имеет указатель на него и может изменять его содержимое без ведома или согласия этого класса. Просто позвони addAll в вашей коллекции — вы получаете значения, хранящиеся в input, и никто не сможет испортить вашу коллекцию.

Collection не лучшее имя переменной. Он не описательный и противоречит имени класса. Рассмотреть возможность profiles или же ftpHostProfiles как имена переменных.

Вам действительно нужны как конструкторы, так и методы для добавления профилей? Это кажется излишним.

Имея addProfile методы return this удобно для создания цепочки, но подумайте, нужно ли вам вместо этого вернуть что-то еще, например, было ли добавление успешным или нет.

getProfile проясняет, что profiles должен быть Map, а не List.

FTP-соединение

Буква `c` должна начинаться с заглавной буквы.

mFtpHostProfiles вероятно должно быть private final.

У этого класса есть несколько неиспользуемых импортов.

Вероятно, вы можете назвать переменную profile Я думаю, что контекста достаточно, чтобы FTPHost является избыточным.

Возможно loggedIn было бы более наглядным, чем status?

Если вы внесли все эти изменения, ваш код мог бы выглядеть примерно так:

FtpConnection:

public final class FtpConnection {

    private final FtpHostProfiles mFtpHostProfiles = new FtpHostProfiles();

    public FtpConnection(FtpHostProfiles input) {
        this.mFtpHostProfiles.addProfiles(input);
    }

    public FTPClient connectftp(String profileName) {
        //  Reference: https://stackoverflow.com/a/8761268/6667035
        FTPClient ftp = new FTPClient();
        try {
            FtpHostProfile profile = mFtpHostProfiles.getProfile(profileName);

            //  Reference: https://stackoverflow.com/a/55950845/6667035
            //  The argument of `FtpClient.connect` method is hostname, not URL.
            ftp.connect(profile.getHostname(), profile.getPort());
            boolean loggedIn = ftp.login(profile.getUsername(), profile.getPassword());
            if (loggedIn) {
                ftp.enterLocalPassiveMode();
                ftp.setFileType(FTP.BINARY_FILE_TYPE);
                ftp.sendCommand("OPTS UTF8 ON");
            }
            System.out.println("status : " + ftp.getStatus());
        } catch (UnknownHostException ex) {
            ex.printStackTrace();
        } catch (SocketException en) {
            en.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return ftp;
    }
}

FtpHostProfile:

public final class FtpHostProfile {
    private final String profileName;
    private final String ftpHostName;
    private final int ftpPort;
    private final String ftpUserName;
    private final String ftpPassword;

    public FtpHostProfile(String hostNameInput, int portInput, String userNameInput, String passwordInput) {
        this.profileName = "";
        this.ftpHostName = hostNameInput;
        this.ftpPort = portInput;
        this.ftpUserName = userNameInput;
        this.ftpPassword = passwordInput;
    }
    public FtpHostProfile(
            String profileNameInput,
            String hostnameInput,
            int portInput,
            String usernameInput,
            String passwordInput) {
        this.profileName = profileNameInput;
        this.ftpHostName = hostnameInput;
        this.ftpPort = portInput;
        this.ftpUserName = usernameInput;
        this.ftpPassword = passwordInput;
    }

    public String getProfileName() {
        return this.profileName;
    }

    public String getHostname() {
        return this.ftpHostName;
    }

    public int getPort() {
        return this.ftpPort;
    }

    public String getUsername() {
        return this.ftpUserName;
    }

    public String getPassword() {
        return this.ftpPassword;
    }
}

FtpHostProfiles:

public final class FtpHostProfiles {
    private final Map<String, FtpHostProfile> profiles = new HashMap<>();

    public FtpHostProfiles() {
        // Empty constructor
    }

    public FtpHostProfiles addProfile(FtpHostProfile input) {
        this.profiles.put(input.getProfileName(), input);
        return this;
    }

    public FtpHostProfiles addProfiles(Collection<FtpHostProfile> input) {
        input.stream().forEach(profile -> profiles.put(profile.getProfileName(), profile));
        return this;
    }

    public FtpHostProfiles addProfiles(FtpHostProfiles input) {
        this.profiles.putAll(input.profiles);
        return this;
    }

    public FtpHostProfile getProfile(String profileNameInput) {
        FtpHostProfile profile = profiles.get(profileNameInput);
        if (profile == null) {
            Log.d("GetProfile", "No such item in stored profiles");
            throw new IllegalStateException("No such item in stored profiles");
        }
        return profile;
    }

}

    Добавить комментарий

    Ваш адрес email не будет опубликован. Обязательные поля помечены *