สิ่งที่เราไม่ได้สังเกต

Apr 09 2023
เกี่ยวกับรูปแบบการออกแบบที่ถูกลืม
วันนี้ฉันกำลังอ่านบทช่วยสอนมากมายและอ่านบทความขนาดกลางเกี่ยวกับการพัฒนา Android ส่วนใหญ่อธิบายเทคโนโลยีใหม่และวิธีที่เราปรับปรุงในแง่ของ UI

วันนี้ฉันกำลังอ่านบทช่วยสอนมากมายและอ่านบทความขนาดกลางเกี่ยวกับการพัฒนา Android ส่วนใหญ่อธิบายเทคโนโลยีใหม่และวิธีที่เราปรับปรุงในแง่ของ UI

ในการเรียนรู้ประจำวันของเรา สิ่งใหม่ๆ มีความสำคัญมาก แต่การทำความเข้าใจสิ่งพื้นฐานก็เช่นกัน ในเวลานี้เราไม่สนใจเกี่ยวกับการจัดการหน่วยความจำเพราะหน่วยความจำโทรศัพท์ของเรามีประสิทธิภาพเทียบเท่ากับคอมพิวเตอร์ทั่วไป ดังนั้นเราจึงไม่สนใจเกี่ยวกับการใช้หน่วยความจำมากนัก .

แม้ว่าหน่วยความจำโทรศัพท์ของเราจะมีประสิทธิภาพ แต่เราต้องดูแลเกี่ยวกับการจัดการหน่วยความจำ ในความคิดของฉัน แอปพลิเคชันที่ดีไม่ได้ขึ้นอยู่กับปริมาณ DAU ในแอป แต่ขึ้นอยู่กับว่าแอปพลิเคชันเหล่านั้นใส่ใจในการปรับรายละเอียดให้เหมาะสมมากน้อยเพียงใด ดังนั้น วันนี้ ผมจะอธิบายถึงการเพิ่มประสิทธิภาพที่สำคัญอย่างหนึ่ง ซึ่งก็คือ การจัดสรรหน่วยความจำ

ในการพัฒนา Android มีหลายวิธีในการเพิ่มประสิทธิภาพการใช้หน่วยความจำและหลีกเลี่ยงการสร้างวัตถุที่ไม่จำเป็น

  1. การรวมออบเจกต์ : นักพัฒนาสามารถใช้กลไกการรวมออบเจกต์ของตนเองได้ โดยที่จำนวนออบเจกต์จะถูกสร้างขึ้นและนำกลับมาใช้ใหม่แทนการสร้างออบเจกต์ใหม่ทุกครั้งที่ต้องการ สิ่งนี้สามารถช่วยลดการใช้หน่วยความจำและปรับปรุงประสิทธิภาพ โดยเฉพาะอย่างยิ่งสำหรับวัตถุที่มีราคาแพงในการสร้าง
  2. Lazy Initialization : อ็อบเจกต์สามารถเริ่มต้นได้อย่าง Lazy Initialization ซึ่งหมายความว่าพวกมันจะถูกสร้างขึ้นเมื่อจำเป็นเท่านั้น สิ่งนี้สามารถช่วยหลีกเลี่ยงการสร้างวัตถุที่ไม่จำเป็นล่วงหน้า โดยเฉพาะอย่างยิ่งสำหรับวัตถุที่อาจไม่จำเป็นในระหว่างวงจรชีวิตของแอปพลิเคชัน
  3. การแคช : นักพัฒนาสามารถแคชวัตถุในหน่วยความจำเพื่อนำกลับมาใช้ใหม่แทนการสร้างวัตถุใหม่ที่มีคุณสมบัติเดียวกัน ซึ่งสามารถทำได้โดยใช้เทคนิคต่างๆ เช่น การใช้แคช LRU (ใช้ล่าสุดน้อยที่สุด) ฐานข้อมูลในหน่วยความจำ หรือกลไกการแคชอื่นๆ ที่จัดทำโดยเฟรมเวิร์ก Android หรือไลบรารีของบุคคลที่สาม
  4. การใช้ออบเจกต์ซ้ำ : นักพัฒนาสามารถออกแบบออบเจกต์ให้สามารถนำมาใช้ซ้ำได้ โดยลดสถานะของออบเจ็กต์ให้เหลือน้อยที่สุดและคงสถานะให้ไร้สถานะ เพื่อให้สามารถใช้กับส่วนประกอบหรือกิจกรรมต่างๆ ได้โดยไม่จำเป็นต้องสร้างออบเจกต์ใหม่
  5. โครงสร้างข้อมูลที่มีประสิทธิภาพ : นักพัฒนาสามารถใช้โครงสร้างข้อมูลที่มีประสิทธิภาพ เช่น อาร์เรย์ที่กระจัดกระจายหรือแผนที่ เพื่อลดการใช้หน่วยความจำเมื่อจัดเก็บและดึงข้อมูล แทนที่จะใช้คอลเลกชันที่อาจสร้างวัตถุที่ไม่จำเป็น
  6. การเพิ่มประสิทธิภาพการทำงาน : นักพัฒนาซอฟต์แวร์สามารถเพิ่มประสิทธิภาพโค้ดของตนเพื่อประสิทธิภาพโดยหลีกเลี่ยงการจัดสรรอ็อบเจกต์ที่ไม่จำเป็น และรับประกันแนวทางปฏิบัติในการจัดการหน่วยความจำอย่างมีประสิทธิภาพ เช่น หลีกเลี่ยงการสร้างออบเจกต์มากเกินไปในลูปที่คับแคบหรือส่วนที่สำคัญต่อประสิทธิภาพของโค้ด

รูปแบบการออกแบบที่ถูกลืมนี้เป็นรูปแบบการออกแบบรุ่น Flyweight เป็นวิธีการสร้างสรรค์ที่ใช้เพื่อเพิ่มประสิทธิภาพการใช้หน่วยความจำโดยการแบ่งปันส่วนทั่วไปของออบเจกต์ระหว่างออบเจ็กต์หลาย ๆ แทนที่จะทำซ้ำ

ได้รับการออกแบบมาโดยเฉพาะเพื่อจัดการกับสถานการณ์ที่จำเป็นต้องสร้างและใช้งานอ็อบเจ็กต์จำนวนมากที่มีคุณสมบัติคล้ายกันในแอปพลิเคชัน แนวคิดหลักเบื้องหลังรูปแบบฟลายเวทคือการแยกสถานะที่แท้จริง (สถานะที่ใช้ร่วมกัน) ของวัตถุออกจากสถานะภายนอก (สถานะเฉพาะ) ของวัตถุ

สถานะภายในประกอบด้วยคุณสมบัติที่เหมือนกันในหลายๆ ออบเจ็กต์และสามารถใช้ร่วมกันได้ ในขณะที่สถานะภายนอกประกอบด้วยคุณสมบัติที่ไม่ซ้ำกับแต่ละอ็อบเจ็กต์และไม่สามารถใช้ร่วมกันได้

โดยทั่วไปแล้วรูปแบบฟลายเวทจะเกี่ยวข้องกับองค์ประกอบต่อไปนี้:

  1. ฟลายเวท : แสดงถึงวัตถุที่ใช้ร่วมกันซึ่งจัดเก็บและนำกลับมาใช้ใหม่ระหว่างวัตถุหลายชิ้น มันมีสถานะที่แท้จริงซึ่งใช้ร่วมกันระหว่างวัตถุ
  2. ConcreteFlyweight : ใช้อินเทอร์เฟซ Flyweight และเป็นตัวแทนของวัตถุที่ใช้ร่วมกันคอนกรีตที่สร้างและนำกลับมาใช้ใหม่
  3. FlyweightFactory : จัดการการสร้างและการแคชวัตถุ Flyweight มันดูแลกลุ่มวัตถุฟลายเวทและจัดหาวิธีการดึงและสร้างวัตถุฟลายเวท
  4. ลูกค้า : เป็นตัวแทนของวัตถุที่ใช้วัตถุฟลายเวท โดยจะจัดเก็บสถานะภายนอกซึ่งเป็นเอกลักษณ์เฉพาะของวัตถุแต่ละชิ้น และส่งต่อไปยังวัตถุรุ่นฟลายเวทเมื่อจำเป็น

// WeatherInfo.kt
data class WeatherInfo(
    val location: String,
    val weather: String,
    val weatherIcon: String
)

// WeatherService.kt
interface WeatherService {
    fun getCurrentLocation(): String
    fun getCurrentWeather(location: String): String
    fun getWeatherIcon(weather: String): String
}

// WeatherInfoFactory.kt
object WeatherInfoFactory {
    private val weatherInfoCache = mutableMapOf<String, WeatherInfo>()

    fun getWeatherInfo(location: String, weather: String): WeatherInfo {
        val key = "$location-$weather"
        if (weatherInfoCache.containsKey(key)) {
            return weatherInfoCache[key]!!
        } else {
            val weatherIcon = getWeatherIcon(weather)
            val weatherInfo = WeatherInfo(location, weather, weatherIcon)
            weatherInfoCache[key] = weatherInfo
            return weatherInfo
        }
    }

    private fun getWeatherIcon(weather: String): String {
        // Implementation for getting weather icon based on weather
    }
}

// ViewModel.kt
class WeatherViewModel(
    private val weatherService: WeatherService
) : ViewModel() {
    private val weatherInfo = MutableLiveData<WeatherInfo>()
    val weatherInfoUI: LiveData<WeatherInfo> get() = weatherInfo

    fun fetchWeatherInfo() {
        viewModelScope.launch {
            try {
                val location = weatherService.getCurrentLocation()
                val weather = weatherService.getCurrentWeather(location)
                val weatherInfo = WeatherInfoFactory.getWeatherInfo(location, weather)
                weatherInfoUI.value = weatherInfo
            } catch (e: Exception) {
                // Handle error
            }
        }
    }
}

// MainActivity.kt
class MainActivity : AppCompatActivity() {
    private val viewModel: WeatherViewModel by viewModels()
    private lateinit var weatherTextView: TextView

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        weatherTextView = findViewById(R.id.weatherTextView)

        viewModel.weatherInfoUI.observe(this, { weatherInfo ->
            weatherTextView.text = "Location: ${weatherInfo.location}\n" +
                    "Weather: ${weatherInfo.weather}\n" +
                    "Icon: ${weatherInfo.weatherIcon}"
        })

        // Fetch weather info
        viewModel.fetchWeatherInfo()
    }
}

ด้วยการใช้รูปแบบการออกแบบ Flyweight เราสามารถหลีกเลี่ยงการสร้างWeatherInfoวัตถุหลายชิ้นที่มีสถานที่และสภาพอากาศเดียวกัน ซึ่งสามารถช่วยเพิ่มประสิทธิภาพการใช้หน่วยความจำเมื่อต้องจัดการกับวัตถุที่คล้ายกันจำนวนมาก สิ่งนี้มีประโยชน์อย่างยิ่งในสถานการณ์ที่การสร้างวัตถุใหม่มีราคาแพงในแง่ของหน่วยความจำและประสิทธิภาพ และมีความจำเป็นต้องนำวัตถุที่มีคุณสมบัติคล้ายกันมาใช้ซ้ำเพื่อลดโอเวอร์เฮดของหน่วยความจำ

ณ จุดนี้ อย่าสับสนระหว่างรูปแบบการออกแบบ Singleton รูปแบบการออกแบบ Flyweight และ Singleton เป็นวิธีการออกแบบเชิงสร้างสรรค์ที่ใช้เพื่อจัดการวัตถุในแอปพลิเคชันซอฟต์แวร์ แต่มีจุดประสงค์ที่แตกต่างกันและมีการใช้งานที่แตกต่างกัน

  1. วัตถุประสงค์ : รูปแบบ Flyweight ใช้เพื่อเพิ่มประสิทธิภาพการใช้หน่วยความจำโดยการแบ่งปันส่วนทั่วไปของออบเจกต์ระหว่างออบเจ็กต์หลาย ๆ ส่วน ในขณะที่รูปแบบ Singleton นั้นใช้เพื่อให้แน่ใจว่าคลาสจะมีเพียงอินสแตนซ์เดียวและให้จุดเข้าถึงส่วนกลางสำหรับอินสแตนซ์นั้น
  2. การแบ่งปันวัตถุ : ในรูปแบบฟลายเวท วัตถุที่มีคุณสมบัติคล้ายกันจะถูกแบ่งปันและใช้ซ้ำเพื่อหลีกเลี่ยงการสร้างวัตถุหลายชิ้นที่มีคุณสมบัติเดียวกัน ซึ่งช่วยลดการใช้หน่วยความจำ ในรูปแบบ Singleton จะมีการสร้างอินสแตนซ์เดียวของคลาสและแชร์ระหว่างออบเจ็กต์ทั้งหมดที่ต้องเข้าถึง เพื่อให้มั่นใจว่ามีเพียงอินสแตนซ์เดียวของคลาสที่มีอยู่ตลอดทั้งแอปพลิเคชัน
  3. การสร้างวัตถุ:ในรูปแบบฟลายเวท วัตถุมักจะสร้างโดยโรงงานหรือวิธีการจากโรงงาน ซึ่งสามารถแคชและนำวัตถุกลับมาใช้ใหม่ตามคุณสมบัติของวัตถุ ในรูปแบบซิงเกิลตัน โดยทั่วไปอินสแตนซ์เดียวของคลาสจะถูกสร้างขึ้นเมื่อเข้าถึงคลาสครั้งแรก และคำขอที่ตามมาสำหรับอินสแตนซ์จะส่งคืนอินสแตนซ์เดียวกันโดยไม่สร้างอินสแตนซ์ใหม่
  4. การจัดการอินสแตนซ์:ในรูปแบบ Flyweight ความรับผิดชอบในการจัดการออบเจกต์ที่ใช้ร่วมกันและการแคชของอ็อบเจ็กต์โดยทั่วไปจะได้รับการจัดการโดยโรงงานหรือวิธีการของโรงงาน ในรูปแบบ Singleton ความรับผิดชอบในการจัดการอินสแตนซ์เดียวของคลาสโดยทั่วไปจะจัดการโดยตัวคลาสเอง
  5. อินสแตนซ์หลายรายการ:รูปแบบ Flyweight ช่วยให้สามารถสร้างหลายอินสแตนซ์ของออบเจกต์ที่คล้ายกันพร้อมคุณสมบัติที่ใช้ร่วมกันได้ ในขณะที่รูปแบบ Singleton จำกัดการสร้างหลายอินสแตนซ์ไว้เพียงอินสแตนซ์เดียวของคลาส
  6. ขอบเขต:โดยทั่วไปแล้วรูปแบบ Flyweight จะใช้ในสถานการณ์ที่มีวัตถุจำนวนมากที่มีคุณสมบัติคล้ายกัน และการปรับหน่วยความจำเป็นเรื่องที่น่ากังวล โดยทั่วไปแล้วรูปแบบ Singleton จะใช้ในสถานการณ์ที่ควรมีเพียงหนึ่งอินสแตนซ์ของคลาสตลอดทั้งแอปพลิเคชัน เช่น สำหรับการจัดการการตั้งค่าคอนฟิกูเรชัน การบันทึก หรือการเชื่อมต่อฐานข้อมูล

ต่อไปนี้คือสาเหตุบางประการที่ทำให้รูปแบบ Flyweight อาจไม่เป็นที่นิยมในหมู่นักพัฒนา:

  1. ความซับซ้อน:การใช้รูปแบบ Flyweight อาจทำให้เกิดความซับซ้อนเพิ่มเติมในรูปแบบของการจัดการสถานะที่ใช้ร่วมกัน การบำรุงรักษากลุ่มของวัตถุ และการจัดการสถานะภายนอก สิ่งนี้อาจต้องการการออกแบบและการใช้งานอย่างระมัดระวัง ซึ่งอาจไม่เป็นที่ต้องการในการใช้งานทั่วไปหรือขนาดเล็ก
  2. กรณีการใช้งานที่จำกัด : รูปแบบ Flyweight มีประโยชน์มากที่สุดในสถานการณ์ที่มีวัตถุจำนวนมากที่มีคุณสมบัติคล้ายกัน และการเพิ่มประสิทธิภาพหน่วยความจำเป็นข้อกังวลที่สำคัญ อย่างไรก็ตาม ไม่ใช่ทุกแอปพลิเคชันที่อาจมีข้อกำหนดดังกล่าว และนักพัฒนาอาจไม่เห็นความจำเป็นในการใช้รูปแบบ Flyweight ในทุกสถานการณ์
  3. ค่าใช้จ่ายด้านประสิทธิภาพ : แม้ว่ารูปแบบ Flyweight สามารถเพิ่มประสิทธิภาพการใช้หน่วยความจำได้ แต่อาจเพิ่มค่าใช้จ่ายเพิ่มเติมในแง่ของการจัดการสถานะที่ใช้ร่วมกันและกลุ่มของวัตถุ สิ่งนี้สามารถส่งผลกระทบต่อประสิทธิภาพ โดยเฉพาะอย่างยิ่งในแอปพลิเคชันที่ประสิทธิภาพเป็นสิ่งสำคัญ และนักพัฒนาอาจเลือกแนวทางอื่นที่ให้การแลกเปลี่ยนที่ดีกว่าระหว่างการใช้หน่วยความจำและประสิทธิภาพ
  4. ทางเลือกที่ง่ายกว่า : ในบางกรณี อาจมีทางเลือกที่ง่ายกว่าเพื่อให้บรรลุเป้าหมายการเพิ่มประสิทธิภาพหน่วยความจำที่คล้ายกัน เช่น การรวมออบเจกต์ การเริ่มต้นแบบขี้เกียจ การแคช หรือเทคนิคการจัดการหน่วยความจำอื่นๆ ซึ่งอาจนำไปใช้และบำรุงรักษาได้ง่ายกว่าเมื่อเทียบกับรูปแบบ Flyweight

ไม่ว่าคุณจะเพิ่มประสิทธิภาพหน่วยความจำของแอปพลิเคชันของคุณในแง่ของรูปแบบ Flyweightหรือการตั้งค่าสำหรับรูปแบบการออกแบบอื่นๆก็ตาม สิ่งสำคัญคือเราต้องระมัดระวังกับแอปพลิเคชันและรหัสของเรา

ขอบคุณที่สละเวลาอ่านบทความนี้ ฉันหวังว่างานเขียนของฉันจะให้ข้อมูลและกระตุ้นความคิด